mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-18 17:37:46 +08:00

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>
252 lines
6.2 KiB
Go
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()
|
|
}
|