buildx/util/imagetools/printers.go
Justin Chadwell 78d8b926db inspect: lazily load attestation data
Delay loading the attestation data immediately, and only compute it upon
request. We do this using a deferred function which allows to define the
computation in the same place as before, but perform the computation
later.

With this patch, we ensure that the attestation data is only pulled from
the remote if it is actually referenced in the format string -
otherwise, we can skip it, for improved performance.

Signed-off-by: Justin Chadwell <me@jedevc.com>
2023-01-24 12:10:57 +00:00

252 lines
6.2 KiB
Go

package imagetools
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"text/tabwriter"
"text/template"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/platforms"
"github.com/docker/distribution/reference"
"github.com/opencontainers/go-digest"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
)
const defaultPfx = " "
type Printer struct {
ctx context.Context
resolver *Resolver
name string
format string
raw []byte
ref reference.Named
manifest ocispecs.Descriptor
index ocispecs.Index
}
func NewPrinter(ctx context.Context, opt Opt, name string, format string) (*Printer, error) {
resolver := New(opt)
ref, err := parseRef(name)
if err != nil {
return nil, err
}
dt, mfst, err := resolver.Get(ctx, ref.String())
if err != nil {
return nil, err
}
var idx ocispecs.Index
if err = json.Unmarshal(dt, &idx); err != nil {
return nil, err
}
return &Printer{
ctx: ctx,
resolver: resolver,
name: name,
format: format,
raw: dt,
ref: ref,
manifest: mfst,
index: idx,
}, nil
}
func (p *Printer) Print(raw bool, out io.Writer) error {
if raw {
_, err := fmt.Fprintf(out, "%s", p.raw) // avoid newline to keep digest
return err
}
if p.format == "" {
w := tabwriter.NewWriter(out, 0, 0, 1, ' ', 0)
_, _ = fmt.Fprintf(w, "Name:\t%s\n", p.ref.String())
_, _ = fmt.Fprintf(w, "MediaType:\t%s\n", p.manifest.MediaType)
_, _ = fmt.Fprintf(w, "Digest:\t%s\n", p.manifest.Digest)
_ = w.Flush()
switch p.manifest.MediaType {
case images.MediaTypeDockerSchema2ManifestList, ocispecs.MediaTypeImageIndex:
if err := p.printManifestList(out); err != nil {
return err
}
}
return nil
}
res, err := newLoader(p.resolver.resolver()).Load(p.ctx, p.name)
if err != nil {
return err
}
tpl, err := template.New("").Funcs(template.FuncMap{
"json": func(v interface{}) string {
b, _ := json.MarshalIndent(v, "", " ")
return string(b)
},
}).Parse(p.format)
if err != nil {
return err
}
imageconfigs := res.Configs()
format := tpl.Root.String()
var mfst interface{}
switch p.manifest.MediaType {
case images.MediaTypeDockerSchema2Manifest, ocispecs.MediaTypeImageManifest:
mfst = p.manifest
case images.MediaTypeDockerSchema2ManifestList, ocispecs.MediaTypeImageIndex:
mfst = struct {
SchemaVersion int `json:"schemaVersion"`
MediaType string `json:"mediaType,omitempty"`
Digest digest.Digest `json:"digest"`
Size int64 `json:"size"`
Manifests []ocispecs.Descriptor `json:"manifests"`
Annotations map[string]string `json:"annotations,omitempty"`
}{
SchemaVersion: p.index.Versioned.SchemaVersion,
MediaType: p.index.MediaType,
Digest: p.manifest.Digest,
Size: p.manifest.Size,
Manifests: p.index.Manifests,
Annotations: p.index.Annotations,
}
}
switch {
// TODO: print formatted config
case strings.HasPrefix(format, "{{.Manifest"):
w := tabwriter.NewWriter(out, 0, 0, 1, ' ', 0)
_, _ = fmt.Fprintf(w, "Name:\t%s\n", p.ref.String())
switch {
case strings.HasPrefix(format, "{{.Manifest"):
_, _ = fmt.Fprintf(w, "MediaType:\t%s\n", p.manifest.MediaType)
_, _ = fmt.Fprintf(w, "Digest:\t%s\n", p.manifest.Digest)
_ = w.Flush()
switch p.manifest.MediaType {
case images.MediaTypeDockerSchema2ManifestList, ocispecs.MediaTypeImageIndex:
_ = p.printManifestList(out)
}
}
default:
if len(res.platforms) > 1 {
return tpl.Execute(out, tplInputs{
Name: p.name,
Manifest: mfst,
Image: imageconfigs,
result: res,
})
}
var ic *ocispecs.Image
for _, v := range imageconfigs {
ic = v
}
return tpl.Execute(out, tplInput{
Name: p.name,
Manifest: mfst,
Image: ic,
result: res,
})
}
return nil
}
func (p *Printer) printManifestList(out io.Writer) error {
w := tabwriter.NewWriter(out, 0, 0, 1, ' ', 0)
_, _ = fmt.Fprintf(w, "\t\n")
_, _ = fmt.Fprintf(w, "Manifests:\t\n")
_ = w.Flush()
w = tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
for i, m := range p.index.Manifests {
if i != 0 {
_, _ = fmt.Fprintf(w, "\t\n")
}
cr, err := reference.WithDigest(p.ref, m.Digest)
if err != nil {
return err
}
_, _ = fmt.Fprintf(w, "%sName:\t%s\n", defaultPfx, cr.String())
_, _ = fmt.Fprintf(w, "%sMediaType:\t%s\n", defaultPfx, m.MediaType)
if p := m.Platform; p != nil {
_, _ = fmt.Fprintf(w, "%sPlatform:\t%s\n", defaultPfx, platforms.Format(*p))
if p.OSVersion != "" {
_, _ = fmt.Fprintf(w, "%sOSVersion:\t%s\n", defaultPfx, p.OSVersion)
}
if len(p.OSFeatures) > 0 {
_, _ = fmt.Fprintf(w, "%sOSFeatures:\t%s\n", defaultPfx, strings.Join(p.OSFeatures, ", "))
}
if len(m.URLs) > 0 {
_, _ = fmt.Fprintf(w, "%sURLs:\t%s\n", defaultPfx, strings.Join(m.URLs, ", "))
}
if len(m.Annotations) > 0 {
_, _ = fmt.Fprintf(w, "%sAnnotations:\t\n", defaultPfx)
_ = w.Flush()
w2 := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
for k, v := range m.Annotations {
_, _ = fmt.Fprintf(w2, "%s%s:\t%s\n", defaultPfx+defaultPfx, k, v)
}
_ = w2.Flush()
}
}
}
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()
}