@@ -25,6 +25,7 @@ import (
2525 "github.com/docker/buildx/util/confutil"
2626 "github.com/docker/buildx/util/desktop"
2727 "github.com/docker/cli/cli/command"
28+ "github.com/docker/cli/cli/command/formatter"
2829 "github.com/docker/cli/cli/debug"
2930 slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
3031 slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
@@ -48,6 +49,7 @@ import (
4849type inspectOptions struct {
4950 builder string
5051 ref string
52+ format string
5153}
5254
5355func runInspect (ctx context.Context , dockerCli command.Cli , opts inspectOptions ) error {
@@ -92,7 +94,135 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
9294 }
9395 st , _ := ls .ReadRef (rec .node .Builder , rec .node .Name , rec .Ref )
9496
95- tw := tabwriter .NewWriter (dockerCli .Out (), 1 , 8 , 1 , '\t' , 0 )
97+ var sg * localstate.StateGroup
98+ if st != nil && st .GroupRef != "" {
99+ sg , _ = ls .ReadGroup (st .GroupRef )
100+ }
101+
102+ switch opts .format {
103+ case formatter .JSONFormatKey :
104+ return inspectPrintJSON (ctx , rec , st , sg , dockerCli .Out ())
105+ case formatter .RawFormatKey :
106+ return inspectPrintRaw (ctx , rec , st , dockerCli .Out ())
107+ default :
108+ return errors .Errorf ("unsupported format %q" , opts .format )
109+ }
110+ }
111+
112+ func inspectPrintJSON (ctx context.Context , rec * historyRecord , ls * localstate.State , lsg * localstate.StateGroup , w io.Writer ) error {
113+ type buildError struct {
114+ Name string `json:"name,omitempty"`
115+ Sources string `json:"sources,omitempty"`
116+ Logs string `json:"logs,omitempty"`
117+ }
118+
119+ out := struct {
120+ Record * controlapi.BuildHistoryRecord `json:"record"`
121+ LocalState * localstate.State `json:"localState,omitempty"`
122+ LocalStateGroup * localstate.StateGroup `json:"localStateGroup,omitempty"`
123+ Name string `json:"name,omitempty"`
124+ DefaultPlatform string `json:"defaultPlatform,omitempty"`
125+ Status string `json:"status,omitempty"`
126+ Duration time.Duration `json:"duration,omitempty"`
127+ Error * buildError `json:"error,omitempty"`
128+ }{
129+ Record : rec .BuildHistoryRecord ,
130+ Name : buildName (rec .FrontendAttrs , ls ),
131+ LocalState : ls ,
132+ LocalStateGroup : lsg ,
133+ Status : "completed" ,
134+ }
135+
136+ if rec .CompletedAt != nil {
137+ out .Duration = rec .CompletedAt .AsTime ().Sub (rec .CreatedAt .AsTime ())
138+ } else {
139+ out .Duration = rec .currentTimestamp .Sub (rec .CreatedAt .AsTime ())
140+ out .Status = "running"
141+ }
142+
143+ c , err := rec .node .Driver .Client (ctx )
144+ if err != nil {
145+ return err
146+ }
147+
148+ workers , err := c .ListWorkers (ctx )
149+ if err != nil {
150+ return errors .Wrap (err , "failed to list workers" )
151+ }
152+ workers0:
153+ for _ , w := range workers {
154+ for _ , p := range w .Platforms {
155+ out .DefaultPlatform = platforms .FormatAll (platforms .Normalize (p ))
156+ break workers0
157+ }
158+ }
159+
160+ store := proxy .NewContentStore (c .ContentClient ())
161+
162+ if rec .Error != nil || rec .ExternalError != nil {
163+ out .Error = new (buildError )
164+ if rec .Error != nil {
165+ if rec .Error .Code == int32 (codes .Canceled ) {
166+ out .Status = "canceled"
167+ } else {
168+ out .Status = "error"
169+ out .Error .Logs = rec .Error .Message
170+ }
171+ }
172+ if rec .ExternalError != nil {
173+ dt , err := content .ReadBlob (ctx , store , ociDesc (rec .ExternalError ))
174+ if err != nil {
175+ return errors .Wrapf (err , "failed to read external error %s" , rec .ExternalError .Digest )
176+ }
177+ var st spb.Status
178+ if err := proto .Unmarshal (dt , & st ); err != nil {
179+ return errors .Wrapf (err , "failed to unmarshal external error %s" , rec .ExternalError .Digest )
180+ }
181+
182+ if st .Code == int32 (codes .Canceled ) {
183+ out .Status = "canceled"
184+ } else {
185+ out .Status = "error"
186+ }
187+
188+ retErr := grpcerrors .FromGRPC (status .ErrorProto (& st ))
189+
190+ var bsources bytes.Buffer
191+ for _ , s := range errdefs .Sources (retErr ) {
192+ s .Print (& bsources )
193+ bsources .WriteString ("\n " )
194+ }
195+ out .Error .Sources = bsources .String ()
196+
197+ var ve * errdefs.VertexError
198+ if errors .As (retErr , & ve ) {
199+ dgst , err := digest .Parse (ve .Vertex .Digest )
200+ if err != nil {
201+ return errors .Wrapf (err , "failed to parse vertex digest %s" , ve .Vertex .Digest )
202+ }
203+ name , logs , err := loadVertexLogs (ctx , c , rec .Ref , dgst , - 1 )
204+ if err != nil {
205+ return errors .Wrapf (err , "failed to load vertex logs %s" , dgst )
206+ }
207+ out .Error .Name = name
208+ if len (logs ) > 0 {
209+ var blogs bytes.Buffer
210+ for _ , l := range logs {
211+ fmt .Fprintln (& blogs , l )
212+ }
213+ out .Error .Logs = blogs .String ()
214+ }
215+ }
216+ }
217+ }
218+
219+ enc := json .NewEncoder (w )
220+ enc .SetIndent ("" , " " )
221+ return enc .Encode (out )
222+ }
223+
224+ func inspectPrintRaw (ctx context.Context , rec * historyRecord , ls * localstate.State , w io.Writer ) error {
225+ tw := tabwriter .NewWriter (w , 1 , 8 , 1 , '\t' , 0 )
96226
97227 attrs := rec .FrontendAttrs
98228 delete (attrs , "frontend.caps" )
@@ -111,9 +241,9 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
111241
112242 var context string
113243 var dockerfile string
114- if st != nil {
115- context = st .LocalPath
116- dockerfile = st .DockerfilePath
244+ if ls != nil {
245+ context = ls .LocalPath
246+ dockerfile = ls .DockerfilePath
117247 wd , _ := os .Getwd ()
118248
119249 if dockerfile != "" && dockerfile != "-" {
@@ -187,11 +317,11 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
187317
188318 tw .Flush ()
189319
190- fmt .Fprintln (dockerCli . Out () )
320+ fmt .Fprintln (w )
191321
192- printTable (dockerCli . Out () , attrs , "context:" , "Named Context" )
322+ printTable (w , attrs , "context:" , "Named Context" )
193323
194- tw = tabwriter .NewWriter (dockerCli . Out () , 1 , 8 , 1 , '\t' , 0 )
324+ tw = tabwriter .NewWriter (w , 1 , 8 , 1 , '\t' , 0 )
195325
196326 fmt .Fprintf (tw , "Started:\t %s\n " , rec .CreatedAt .AsTime ().Local ().Format ("2006-01-02 15:04:05" ))
197327 var duration time.Duration
@@ -213,9 +343,9 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
213343 fmt .Fprintf (tw , "Build Steps:\t %d/%d (%.0f%% cached)\n " , rec .NumCompletedSteps , rec .NumTotalSteps , float64 (rec .NumCachedSteps )/ float64 (rec .NumTotalSteps )* 100 )
214344 tw .Flush ()
215345
216- fmt .Fprintln (dockerCli . Out () )
346+ fmt .Fprintln (w )
217347
218- tw = tabwriter .NewWriter (dockerCli . Out () , 1 , 8 , 1 , '\t' , 0 )
348+ tw = tabwriter .NewWriter (w , 1 , 8 , 1 , '\t' , 0 )
219349
220350 writeAttr ("force-network-mode" , "Network" , nil )
221351 writeAttr ("hostname" , "Hostname" , nil )
@@ -260,10 +390,10 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
260390
261391 tw .Flush ()
262392
263- fmt .Fprintln (dockerCli . Out () )
393+ fmt .Fprintln (w )
264394
265- printTable (dockerCli . Out () , attrs , "build-arg:" , "Build Arg" )
266- printTable (dockerCli . Out () , attrs , "label:" , "Label" )
395+ printTable (w , attrs , "build-arg:" , "Build Arg" )
396+ printTable (w , attrs , "label:" , "Label" )
267397
268398 c , err := rec .node .Driver .Client (ctx )
269399 if err != nil {
@@ -293,19 +423,19 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
293423 return errors .Errorf ("failed to unmarshal provenance %s: %v" , prov .descr .Digest , err )
294424 }
295425
296- fmt .Fprintln (dockerCli . Out () , "Materials:" )
297- tw = tabwriter .NewWriter (dockerCli . Out () , 1 , 8 , 1 , '\t' , 0 )
426+ fmt .Fprintln (w , "Materials:" )
427+ tw = tabwriter .NewWriter (w , 1 , 8 , 1 , '\t' , 0 )
298428 fmt .Fprintf (tw , "URI\t DIGEST\n " )
299429 for _ , m := range pred .Materials {
300430 fmt .Fprintf (tw , "%s\t %s\n " , m .URI , strings .Join (digestSetToDigests (m .Digest ), ", " ))
301431 }
302432 tw .Flush ()
303- fmt .Fprintln (dockerCli . Out () )
433+ fmt .Fprintln (w )
304434 }
305435
306436 if len (attachments ) > 0 {
307437 fmt .Fprintf (tw , "Attachments:\n " )
308- tw = tabwriter .NewWriter (dockerCli . Out () , 1 , 8 , 1 , '\t' , 0 )
438+ tw = tabwriter .NewWriter (w , 1 , 8 , 1 , '\t' , 0 )
309439 fmt .Fprintf (tw , "DIGEST\t PLATFORM\t TYPE\n " )
310440 for _ , a := range attachments {
311441 p := ""
@@ -315,7 +445,7 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
315445 fmt .Fprintf (tw , "%s\t %s\t %s\n " , a .descr .Digest , p , descrType (a .descr ))
316446 }
317447 tw .Flush ()
318- fmt .Fprintln (dockerCli . Out () )
448+ fmt .Fprintln (w )
319449 }
320450
321451 if rec .ExternalError != nil {
@@ -329,9 +459,9 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
329459 }
330460 retErr := grpcerrors .FromGRPC (status .ErrorProto (& st ))
331461 for _ , s := range errdefs .Sources (retErr ) {
332- s .Print (dockerCli . Out () )
462+ s .Print (w )
333463 }
334- fmt .Fprintln (dockerCli . Out () )
464+ fmt .Fprintln (w )
335465
336466 var ve * errdefs.VertexError
337467 if errors .As (retErr , & ve ) {
@@ -344,25 +474,25 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
344474 return errors .Wrapf (err , "failed to load vertex logs %s" , dgst )
345475 }
346476 if len (logs ) > 0 {
347- fmt .Fprintln (dockerCli . Out () , "Logs:" )
348- fmt .Fprintf (dockerCli . Out () , "> => %s:\n " , name )
477+ fmt .Fprintln (w , "Logs:" )
478+ fmt .Fprintf (w , "> => %s:\n " , name )
349479 for _ , l := range logs {
350- fmt .Fprintln (dockerCli . Out () , "> " + l )
480+ fmt .Fprintln (w , "> " + l )
351481 }
352- fmt .Fprintln (dockerCli . Out () )
482+ fmt .Fprintln (w )
353483 }
354484 }
355485
356486 if debug .IsEnabled () {
357- fmt .Fprintf (dockerCli . Out () , "\n %+v\n " , stack .Formatter (retErr ))
487+ fmt .Fprintf (w , "\n %+v\n " , stack .Formatter (retErr ))
358488 } else if len (stack .Traces (retErr )) > 0 {
359- fmt .Fprintf (dockerCli . Out () , "Enable --debug to see stack traces for error\n " )
489+ fmt .Fprintf (w , "Enable --debug to see stack traces for error\n " )
360490 }
361491 }
362492
363- fmt .Fprintf (dockerCli . Out () , "Print build logs: docker buildx history logs %s\n " , rec .Ref )
493+ fmt .Fprintf (w , "Print build logs: docker buildx history logs %s\n " , rec .Ref )
364494
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 )))
495+ fmt .Fprintf (w , "View build in Docker Desktop: %s\n " , desktop .BuildURL (fmt .Sprintf ("%s/%s/%s" , rec .node .Builder , rec .node .Name , rec .Ref )))
366496
367497 return nil
368498}
@@ -388,7 +518,8 @@ func inspectCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
388518 attachmentCmd (dockerCli , rootOpts ),
389519 )
390520
391- // flags := cmd.Flags()
521+ flags := cmd .Flags ()
522+ flags .StringVar (& options .format , "format" , formatter .RawFormatKey , "Format the output" )
392523
393524 return cmd
394525}
0 commit comments