11package history
22
33import (
4+ "cmp"
45 "context"
6+ "encoding/json"
57 "fmt"
68 "io"
7- "log"
89 "os"
910 "path/filepath"
1011 "slices"
@@ -13,12 +14,22 @@ import (
1314 "text/tabwriter"
1415 "time"
1516
17+ "github.com/containerd/containerd/content"
18+ "github.com/containerd/containerd/content/proxy"
19+ "github.com/containerd/containerd/images"
1620 "github.com/containerd/platforms"
1721 "github.com/docker/buildx/builder"
1822 "github.com/docker/buildx/localstate"
1923 "github.com/docker/buildx/util/cobrautil/completion"
2024 "github.com/docker/buildx/util/confutil"
25+ "github.com/docker/buildx/util/desktop"
2126 "github.com/docker/cli/cli/command"
27+ slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
28+ slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
29+ controlapi "github.com/moby/buildkit/api/services/control"
30+ provenancetypes "github.com/moby/buildkit/solver/llbsolver/provenance/types"
31+ "github.com/opencontainers/go-digest"
32+ ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
2233 "github.com/pkg/errors"
2334 "github.com/spf13/cobra"
2435 "github.com/tonistiigi/go-csvvalue"
@@ -72,9 +83,6 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
7283 }
7384 st , _ := ls .ReadRef (rec .node .Builder , rec .node .Name , rec .Ref )
7485
75- log .Printf ("rec %+v" , rec )
76- log .Printf ("st %+v" , st )
77-
7886 tw := tabwriter .NewWriter (dockerCli .Out (), 1 , 8 , 1 , '\t' , 0 )
7987
8088 attrs := rec .FrontendAttrs
@@ -101,12 +109,16 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
101109
102110 if dockerfile != "" && dockerfile != "-" {
103111 if rel , err := filepath .Rel (context , dockerfile ); err == nil {
104- dockerfile = rel
112+ if ! strings .HasPrefix (rel , ".." + string (filepath .Separator )) {
113+ dockerfile = rel
114+ }
105115 }
106116 }
107117 if context != "" {
108118 if rel , err := filepath .Rel (wd , context ); err == nil {
109- context = rel
119+ if ! strings .HasPrefix (rel , ".." + string (filepath .Separator )) {
120+ context = rel
121+ }
110122 }
111123 }
112124 }
@@ -226,7 +238,7 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
226238
227239 var unusedAttrs []string
228240 for k := range attrs {
229- if strings .HasPrefix (k , "vcs:" ) || strings .HasPrefix (k , "build-arg:" ) || strings .HasPrefix (k , "label:" ) || strings .HasPrefix (k , "context:" ) {
241+ if strings .HasPrefix (k , "vcs:" ) || strings .HasPrefix (k , "build-arg:" ) || strings .HasPrefix (k , "label:" ) || strings .HasPrefix (k , "context:" ) || strings . HasPrefix ( k , "attest:" ) {
230242 continue
231243 }
232244 unusedAttrs = append (unusedAttrs , k )
@@ -244,10 +256,62 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
244256 printTable (dockerCli .Out (), attrs , "build-arg:" , "Build Arg" )
245257 printTable (dockerCli .Out (), attrs , "label:" , "Label" )
246258
247- // exporters (image)
248- // commands
249- // error
250- // materials
259+ c , err := rec .node .Driver .Client (ctx )
260+ if err != nil {
261+ return err
262+ }
263+
264+ store := proxy .NewContentStore (c .ContentClient ())
265+
266+ attachments , err := allAttachments (ctx , store , * rec )
267+ if err != nil {
268+ return err
269+ }
270+
271+ provIndex := slices .IndexFunc (attachments , func (a attachment ) bool {
272+ return descrType (a .descr ) == slsa02 .PredicateSLSAProvenance
273+ })
274+ if provIndex != - 1 {
275+ prov := attachments [provIndex ]
276+
277+ dt , err := content .ReadBlob (ctx , store , prov .descr )
278+ if err != nil {
279+ return errors .Errorf ("failed to read provenance %s: %v" , prov .descr .Digest , err )
280+ }
281+
282+ var pred provenancetypes.ProvenancePredicate
283+ if err := json .Unmarshal (dt , & pred ); err != nil {
284+ return errors .Errorf ("failed to unmarshal provenance %s: %v" , prov .descr .Digest , err )
285+ }
286+
287+ fmt .Fprintln (dockerCli .Out (), "Materials:" )
288+ tw = tabwriter .NewWriter (dockerCli .Out (), 1 , 8 , 1 , '\t' , 0 )
289+ fmt .Fprintf (tw , "URI\t DIGEST\n " )
290+ for _ , m := range pred .Materials {
291+ fmt .Fprintf (tw , "%s\t %s\n " , m .URI , strings .Join (digestSetToDigests (m .Digest ), ", " ))
292+ }
293+ tw .Flush ()
294+ fmt .Fprintln (dockerCli .Out ())
295+ }
296+
297+ if len (attachments ) > 0 {
298+ fmt .Fprintf (tw , "Attachments:\n " )
299+ tw = tabwriter .NewWriter (dockerCli .Out (), 1 , 8 , 1 , '\t' , 0 )
300+ fmt .Fprintf (tw , "DIGEST\t PLATFORM\t TYPE\n " )
301+ for _ , a := range attachments {
302+ p := ""
303+ if a .platform != nil {
304+ p = platforms .FormatAll (* a .platform )
305+ }
306+ fmt .Fprintf (tw , "%s\t %s\t %s\n " , a .descr .Digest , p , descrType (a .descr ))
307+ }
308+ tw .Flush ()
309+ fmt .Fprintln (dockerCli .Out ())
310+ }
311+
312+ fmt .Fprintf (dockerCli .Out (), "Print build logs: docker buildx history logs %s\n " , rec .Ref )
313+
314+ fmt .Fprintf (dockerCli .Out (), "View build in Docker Desktop: %s\n " , desktop .BuildURL (rec .Ref ))
251315
252316 return nil
253317}
@@ -274,6 +338,111 @@ func inspectCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
274338 return cmd
275339}
276340
341+ type attachment struct {
342+ platform * ocispecs.Platform
343+ descr ocispecs.Descriptor
344+ }
345+
346+ func allAttachments (ctx context.Context , store content.Store , rec historyRecord ) ([]attachment , error ) {
347+ var attachments []attachment
348+
349+ if rec .Result != nil {
350+ for _ , a := range rec .Result .Attestations {
351+ attachments = append (attachments , attachment {
352+ descr : ociDesc (a ),
353+ })
354+ }
355+ for _ , r := range rec .Result .Results {
356+ attachments = append (attachments , walkAttachments (ctx , store , ociDesc (r ), nil )... )
357+ }
358+ }
359+
360+ for key , ri := range rec .Results {
361+ p , err := platforms .Parse (key )
362+ if err != nil {
363+ return nil , err
364+ }
365+ for _ , a := range ri .Attestations {
366+ attachments = append (attachments , attachment {
367+ platform : & p ,
368+ descr : ociDesc (a ),
369+ })
370+ }
371+ for _ , r := range ri .Results {
372+ attachments = append (attachments , walkAttachments (ctx , store , ociDesc (r ), & p )... )
373+ }
374+ }
375+
376+ slices .SortFunc (attachments , func (a , b attachment ) int {
377+ pCmp := 0
378+ if a .platform == nil && b .platform != nil {
379+ return - 1
380+ } else if a .platform != nil && b .platform == nil {
381+ return 1
382+ } else if a .platform != nil && b .platform != nil {
383+ pCmp = cmp .Compare (platforms .FormatAll (* a .platform ), platforms .FormatAll (* b .platform ))
384+ }
385+ return cmp .Or (
386+ pCmp ,
387+ cmp .Compare (descrType (a .descr ), descrType (b .descr )),
388+ )
389+ })
390+
391+ return attachments , nil
392+ }
393+
394+ func walkAttachments (ctx context.Context , store content.Store , desc ocispecs.Descriptor , platform * ocispecs.Platform ) []attachment {
395+ _ , err := store .Info (ctx , desc .Digest )
396+ if err != nil {
397+ return nil
398+ }
399+
400+ var out []attachment
401+
402+ if desc .Annotations ["vnd.docker.reference.type" ] != "attestation-manifest" {
403+ out = append (out , attachment {platform : platform , descr : desc })
404+ }
405+
406+ if desc .MediaType != ocispecs .MediaTypeImageIndex && desc .MediaType != images .MediaTypeDockerSchema2ManifestList {
407+ return out
408+ }
409+
410+ dt , err := content .ReadBlob (ctx , store , desc )
411+ if err != nil {
412+ return out
413+ }
414+
415+ var idx ocispecs.Index
416+ if err := json .Unmarshal (dt , & idx ); err != nil {
417+ return out
418+ }
419+
420+ for _ , d := range idx .Manifests {
421+ p := platform
422+ if d .Platform != nil {
423+ p = d .Platform
424+ }
425+ out = append (out , walkAttachments (ctx , store , d , p )... )
426+ }
427+
428+ return out
429+ }
430+
431+ func ociDesc (in * controlapi.Descriptor ) ocispecs.Descriptor {
432+ return ocispecs.Descriptor {
433+ MediaType : in .MediaType ,
434+ Digest : digest .Digest (in .Digest ),
435+ Size : in .Size ,
436+ Annotations : in .Annotations ,
437+ }
438+ }
439+ func descrType (desc ocispecs.Descriptor ) string {
440+ if typ , ok := desc .Annotations ["in-toto.io/predicate-type" ]; ok {
441+ return typ
442+ }
443+ return desc .MediaType
444+ }
445+
277446func tryParseValue (s string , f func (string ) (string , error )) string {
278447 v , err := f (s )
279448 if err != nil {
@@ -303,3 +472,11 @@ func printTable(w io.Writer, attrs map[string]string, prefix, title string) {
303472 tw .Flush ()
304473 fmt .Fprintln (w )
305474}
475+
476+ func digestSetToDigests (ds slsa.DigestSet ) []string {
477+ var out []string
478+ for k , v := range ds {
479+ out = append (out , fmt .Sprintf ("%s:%s" , k , v ))
480+ }
481+ return out
482+ }
0 commit comments