mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 10:03:42 +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()
 | 
						|
}
 |