diff --git a/util/imagetools/loader.go b/util/imagetools/loader.go index 86f5a745..9bba4e21 100644 --- a/util/imagetools/loader.go +++ b/util/imagetools/loader.go @@ -49,6 +49,9 @@ type asset struct { config *ocispec.Image sbom *sbomStub provenance *provenanceStub + + deferredSbom func() (*sbomStub, error) + deferredProvenance func() (*provenanceStub, error) } type result struct { @@ -261,36 +264,40 @@ type sbomStub struct { func (l *loader) scanSBOM(ctx context.Context, fetcher remotes.Fetcher, r *result, refs []digest.Digest, as *asset) error { ctx = remotes.WithMediaTypeKeyPrefix(ctx, "application/vnd.in-toto+json", "intoto") - for _, dgst := range refs { - mfst, ok := r.manifests[dgst] - if !ok { - return errors.Errorf("referenced image %s not found", dgst) - } - for _, layer := range mfst.manifest.Layers { - if layer.MediaType == "application/vnd.in-toto+json" && layer.Annotations["in-toto.io/predicate-type"] == "https://spdx.dev/Document" { - _, err := remotes.FetchHandler(l.cache, fetcher)(ctx, layer) - if err != nil { - return err - } - dt, err := content.ReadBlob(ctx, l.cache, layer) - if err != nil { - return err - } - var spdx struct { - Predicate interface{} `json:"predicate"` - } - if err := json.Unmarshal(dt, &spdx); err != nil { - return err - } - - if as.sbom == nil { - as.sbom = &sbomStub{} - as.sbom.SPDX = spdx.Predicate - } else { - as.sbom.AdditionalSPDXs = append(as.sbom.AdditionalSPDXs, spdx.Predicate) + as.deferredSbom = func() (*sbomStub, error) { + var sbom *sbomStub + for _, dgst := range refs { + mfst, ok := r.manifests[dgst] + if !ok { + return nil, errors.Errorf("referenced image %s not found", dgst) + } + for _, layer := range mfst.manifest.Layers { + if layer.MediaType == "application/vnd.in-toto+json" && layer.Annotations["in-toto.io/predicate-type"] == "https://spdx.dev/Document" { + _, err := remotes.FetchHandler(l.cache, fetcher)(ctx, layer) + if err != nil { + return nil, err + } + dt, err := content.ReadBlob(ctx, l.cache, layer) + if err != nil { + return nil, err + } + var spdx struct { + Predicate interface{} `json:"predicate"` + } + if err := json.Unmarshal(dt, &spdx); err != nil { + return nil, err + } + + if sbom == nil { + sbom = &sbomStub{} + sbom.SPDX = spdx.Predicate + } else { + sbom.AdditionalSPDXs = append(sbom.AdditionalSPDXs, spdx.Predicate) + } } } } + return sbom, nil } return nil } @@ -301,33 +308,37 @@ type provenanceStub struct { func (l *loader) scanProvenance(ctx context.Context, fetcher remotes.Fetcher, r *result, refs []digest.Digest, as *asset) error { ctx = remotes.WithMediaTypeKeyPrefix(ctx, "application/vnd.in-toto+json", "intoto") - for _, dgst := range refs { - mfst, ok := r.manifests[dgst] - if !ok { - return errors.Errorf("referenced image %s not found", dgst) - } - for _, layer := range mfst.manifest.Layers { - if layer.MediaType == "application/vnd.in-toto+json" && strings.HasPrefix(layer.Annotations["in-toto.io/predicate-type"], "https://slsa.dev/provenance/") { - _, err := remotes.FetchHandler(l.cache, fetcher)(ctx, layer) - if err != nil { - return err - } - dt, err := content.ReadBlob(ctx, l.cache, layer) - if err != nil { - return err - } - var slsa struct { - Predicate interface{} `json:"predicate"` - } - if err := json.Unmarshal(dt, &slsa); err != nil { - return err - } - as.provenance = &provenanceStub{ - SLSA: slsa.Predicate, + as.deferredProvenance = func() (*provenanceStub, error) { + var provenance *provenanceStub + for _, dgst := range refs { + mfst, ok := r.manifests[dgst] + if !ok { + return nil, errors.Errorf("referenced image %s not found", dgst) + } + for _, layer := range mfst.manifest.Layers { + if layer.MediaType == "application/vnd.in-toto+json" && strings.HasPrefix(layer.Annotations["in-toto.io/predicate-type"], "https://slsa.dev/provenance/") { + _, err := remotes.FetchHandler(l.cache, fetcher)(ctx, layer) + if err != nil { + return nil, err + } + dt, err := content.ReadBlob(ctx, l.cache, layer) + if err != nil { + return nil, err + } + var slsa struct { + Predicate interface{} `json:"predicate"` + } + if err := json.Unmarshal(dt, &slsa); err != nil { + return nil, err + } + provenance = &provenanceStub{ + SLSA: slsa.Predicate, + } + break } - break } } + return provenance, nil } return nil } @@ -346,30 +357,50 @@ func (r *result) Configs() map[string]*ocispec.Image { return res } -func (r *result) Provenance() map[string]provenanceStub { +func (r *result) Provenance() (map[string]provenanceStub, error) { if len(r.assets) == 0 { - return nil + return nil, nil } res := make(map[string]provenanceStub) for p, a := range r.assets { - if a.provenance == nil { + if a.deferredProvenance == nil { continue } + if a.provenance == nil { + provenance, err := a.deferredProvenance() + if err != nil { + return nil, err + } + if provenance == nil { + continue + } + a.provenance = provenance + } res[p] = *a.provenance } - return res + return res, nil } -func (r *result) SBOM() map[string]sbomStub { +func (r *result) SBOM() (map[string]sbomStub, error) { if len(r.assets) == 0 { - return nil + return nil, nil } res := make(map[string]sbomStub) for p, a := range r.assets { - if a.sbom == nil { + if a.deferredSbom == nil { continue } + if a.sbom == nil { + sbom, err := a.deferredSbom() + if err != nil { + return nil, err + } + if sbom == nil { + continue + } + a.sbom = sbom + } res[p] = *a.sbom } - return res + return res, nil } diff --git a/util/imagetools/printers.go b/util/imagetools/printers.go index e1768353..78041cc7 100644 --- a/util/imagetools/printers.go +++ b/util/imagetools/printers.go @@ -99,8 +99,6 @@ func (p *Printer) Print(raw bool, out io.Writer) error { } imageconfigs := res.Configs() - provenances := res.Provenance() - sboms := res.SBOM() format := tpl.Root.String() var mfst interface{} @@ -142,44 +140,22 @@ func (p *Printer) Print(raw bool, out io.Writer) error { } default: if len(res.platforms) > 1 { - return tpl.Execute(out, struct { - Name string `json:"name,omitempty"` - Manifest interface{} `json:"manifest,omitempty"` - Image map[string]*ocispecs.Image `json:"image,omitempty"` - Provenance map[string]provenanceStub `json:"Provenance,omitempty"` - SBOM map[string]sbomStub `json:"SBOM,omitempty"` - }{ - Name: p.name, - Manifest: mfst, - Image: imageconfigs, - Provenance: provenances, - SBOM: sboms, + return tpl.Execute(out, tplInputs{ + Name: p.name, + Manifest: mfst, + Image: imageconfigs, + result: res, }) } var ic *ocispecs.Image for _, v := range imageconfigs { ic = v } - var provenance provenanceStub - for _, v := range provenances { - provenance = v - } - var sbom sbomStub - for _, v := range sboms { - sbom = v - } - return tpl.Execute(out, struct { - Name string `json:"name,omitempty"` - Manifest interface{} `json:"manifest,omitempty"` - Image *ocispecs.Image `json:"image,omitempty"` - Provenance provenanceStub `json:"Provenance,omitempty"` - SBOM sbomStub `json:"SBOM,omitempty"` - }{ - Name: p.name, - Manifest: mfst, - Image: ic, - Provenance: provenance, - SBOM: sbom, + return tpl.Execute(out, tplInput{ + Name: p.name, + Manifest: mfst, + Image: ic, + result: res, }) } @@ -227,3 +203,49 @@ func (p *Printer) printManifestList(out io.Writer) error { } return w.Flush() } + +type tplInput struct { + Name string `json:"name,omitempty"` + Manifest interface{} `json:"manifest,omitempty"` + Image *ocispecs.Image `json:"image,omitempty"` + + result *result +} + +func (inp tplInput) SBOM() (sbomStub, error) { + sbom, err := inp.result.SBOM() + if err != nil { + return sbomStub{}, nil + } + for _, v := range sbom { + return v, nil + } + return sbomStub{}, nil +} + +func (inp tplInput) Provenance() (provenanceStub, error) { + provenance, err := inp.result.Provenance() + if err != nil { + return provenanceStub{}, nil + } + for _, v := range provenance { + return v, nil + } + return provenanceStub{}, nil +} + +type tplInputs struct { + Name string `json:"name,omitempty"` + Manifest interface{} `json:"manifest,omitempty"` + Image map[string]*ocispecs.Image `json:"image,omitempty"` + + result *result +} + +func (inp tplInputs) SBOM() (map[string]sbomStub, error) { + return inp.result.SBOM() +} + +func (inp tplInputs) Provenance() (map[string]provenanceStub, error) { + return inp.result.Provenance() +}