mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-01 00:23:56 +08:00 
			
		
		
		
	imagetools inspect: handle provenance and sbom
use stub structs for SLSA/SBOM while waiting for go-imageinspect library to be public. Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
		
							
								
								
									
										357
									
								
								util/imagetools/loader.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										357
									
								
								util/imagetools/loader.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,357 @@ | ||||
| package imagetools | ||||
|  | ||||
| // TODO: replace with go-imageinspect library when public | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/containerd/containerd/content" | ||||
| 	"github.com/containerd/containerd/images" | ||||
| 	"github.com/containerd/containerd/platforms" | ||||
| 	"github.com/containerd/containerd/remotes" | ||||
| 	"github.com/docker/distribution/reference" | ||||
| 	"github.com/moby/buildkit/util/contentutil" | ||||
| 	"github.com/opencontainers/go-digest" | ||||
| 	ocispec "github.com/opencontainers/image-spec/specs-go/v1" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"golang.org/x/sync/errgroup" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	annotationReference = "vnd.docker.reference.digest" | ||||
| ) | ||||
|  | ||||
| type contentCache interface { | ||||
| 	content.Provider | ||||
| 	content.Ingester | ||||
| } | ||||
|  | ||||
| type loader struct { | ||||
| 	resolver remotes.Resolver | ||||
| 	cache    contentCache | ||||
| } | ||||
|  | ||||
| type manifest struct { | ||||
| 	desc     ocispec.Descriptor | ||||
| 	manifest ocispec.Manifest | ||||
| } | ||||
|  | ||||
| type index struct { | ||||
| 	desc  ocispec.Descriptor | ||||
| 	index ocispec.Index | ||||
| } | ||||
|  | ||||
| type asset struct { | ||||
| 	config *ocispec.Image | ||||
| 	sbom   *sbomStub | ||||
| 	slsa   *slsaStub | ||||
| } | ||||
|  | ||||
| type result struct { | ||||
| 	mu        sync.Mutex | ||||
| 	indexes   map[digest.Digest]index | ||||
| 	manifests map[digest.Digest]manifest | ||||
| 	images    map[string]digest.Digest | ||||
| 	refs      map[digest.Digest][]digest.Digest | ||||
|  | ||||
| 	platforms []string | ||||
| 	assets    map[string]asset | ||||
| } | ||||
|  | ||||
| func newLoader(resolver remotes.Resolver) *loader { | ||||
| 	return &loader{ | ||||
| 		resolver: resolver, | ||||
| 		cache:    contentutil.NewBuffer(), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (l *loader) Load(ctx context.Context, ref string) (*result, error) { | ||||
| 	named, err := parseRef(ref) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	_, desc, err := l.resolver.Resolve(ctx, named.String()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	canonical, err := reference.WithDigest(named, desc.Digest) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	fetcher, err := l.resolver.Fetcher(ctx, canonical.String()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	r := &result{ | ||||
| 		indexes:   make(map[digest.Digest]index), | ||||
| 		manifests: make(map[digest.Digest]manifest), | ||||
| 		images:    make(map[string]digest.Digest), | ||||
| 		refs:      make(map[digest.Digest][]digest.Digest), | ||||
| 		assets:    make(map[string]asset), | ||||
| 	} | ||||
|  | ||||
| 	if err := l.fetch(ctx, fetcher, desc, r); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	for platform, dgst := range r.images { | ||||
| 		r.platforms = append(r.platforms, platform) | ||||
|  | ||||
| 		mfst, ok := r.manifests[dgst] | ||||
| 		if !ok { | ||||
| 			return nil, errors.Errorf("image %s not found", platform) | ||||
| 		} | ||||
|  | ||||
| 		var a asset | ||||
| 		annotations := make(map[string]string, len(mfst.manifest.Annotations)+len(mfst.desc.Annotations)) | ||||
| 		for k, v := range mfst.desc.Annotations { | ||||
| 			annotations[k] = v | ||||
| 		} | ||||
| 		for k, v := range mfst.manifest.Annotations { | ||||
| 			annotations[k] = v | ||||
| 		} | ||||
|  | ||||
| 		if err := l.scanConfig(ctx, fetcher, mfst.manifest.Config, &a); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		refs, ok := r.refs[dgst] | ||||
| 		if ok { | ||||
| 			if err := l.scanSBOM(ctx, fetcher, r, refs, &a); err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if err := l.scanProvenance(ctx, fetcher, r, refs, &a); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		r.assets[platform] = a | ||||
| 	} | ||||
|  | ||||
| 	sort.Strings(r.platforms) | ||||
| 	return r, nil | ||||
| } | ||||
|  | ||||
| func (l *loader) fetch(ctx context.Context, fetcher remotes.Fetcher, desc ocispec.Descriptor, r *result) error { | ||||
| 	_, err := remotes.FetchHandler(l.cache, fetcher)(ctx, desc) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	switch desc.MediaType { | ||||
| 	case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest: | ||||
| 		var mfst ocispec.Manifest | ||||
| 		dt, err := content.ReadBlob(ctx, l.cache, desc) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := json.Unmarshal(dt, &mfst); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		r.mu.Lock() | ||||
| 		r.manifests[desc.Digest] = manifest{ | ||||
| 			desc:     desc, | ||||
| 			manifest: mfst, | ||||
| 		} | ||||
| 		r.mu.Unlock() | ||||
|  | ||||
| 		ref, ok := desc.Annotations[annotationReference] | ||||
| 		if ok { | ||||
| 			refdgst, err := digest.Parse(ref) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			r.mu.Lock() | ||||
| 			r.refs[refdgst] = append(r.refs[refdgst], desc.Digest) | ||||
| 			r.mu.Unlock() | ||||
| 		} else { | ||||
| 			p := desc.Platform | ||||
| 			if p == nil { | ||||
| 				p, err = l.readPlatformFromConfig(ctx, fetcher, mfst.Config) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 			r.mu.Lock() | ||||
| 			r.images[platforms.Format(platforms.Normalize(*p))] = desc.Digest | ||||
| 			r.mu.Unlock() | ||||
| 		} | ||||
| 	case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex: | ||||
| 		var idx ocispec.Index | ||||
| 		dt, err := content.ReadBlob(ctx, l.cache, desc) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		if err := json.Unmarshal(dt, &idx); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		r.mu.Lock() | ||||
| 		r.indexes[desc.Digest] = index{ | ||||
| 			desc:  desc, | ||||
| 			index: idx, | ||||
| 		} | ||||
| 		r.mu.Unlock() | ||||
|  | ||||
| 		eg, ctx := errgroup.WithContext(ctx) | ||||
| 		for _, d := range idx.Manifests { | ||||
| 			d := d | ||||
| 			eg.Go(func() error { | ||||
| 				return l.fetch(ctx, fetcher, d, r) | ||||
| 			}) | ||||
| 		} | ||||
|  | ||||
| 		if err := eg.Wait(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	default: | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (l *loader) readPlatformFromConfig(ctx context.Context, fetcher remotes.Fetcher, desc ocispec.Descriptor) (*ocispec.Platform, error) { | ||||
| 	_, err := remotes.FetchHandler(l.cache, fetcher)(ctx, desc) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	dt, err := content.ReadBlob(ctx, l.cache, desc) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	var config ocispec.Image | ||||
| 	if err := json.Unmarshal(dt, &config); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return &ocispec.Platform{ | ||||
| 		OS:           config.OS, | ||||
| 		Architecture: config.Architecture, | ||||
| 		Variant:      config.Variant, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func (l *loader) scanConfig(ctx context.Context, fetcher remotes.Fetcher, desc ocispec.Descriptor, as *asset) error { | ||||
| 	_, err := remotes.FetchHandler(l.cache, fetcher)(ctx, desc) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	dt, err := content.ReadBlob(ctx, l.cache, desc) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return json.Unmarshal(dt, &as.config) | ||||
| } | ||||
|  | ||||
| type sbomStub struct { | ||||
| 	SPDX json.RawMessage `json:",omitempty"` | ||||
| } | ||||
|  | ||||
| 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 | ||||
| 				} | ||||
| 				as.sbom = &sbomStub{ | ||||
| 					SPDX: dt, | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type slsaStub struct { | ||||
| 	Provenance json.RawMessage `json:",omitempty"` | ||||
| } | ||||
|  | ||||
| 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 | ||||
| 				} | ||||
| 				as.slsa = &slsaStub{ | ||||
| 					Provenance: dt, | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (r *result) Configs() map[string]*ocispec.Image { | ||||
| 	if len(r.assets) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	res := make(map[string]*ocispec.Image) | ||||
| 	for p, a := range r.assets { | ||||
| 		if a.config == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		res[p] = a.config | ||||
| 	} | ||||
| 	return res | ||||
| } | ||||
|  | ||||
| func (r *result) SLSA() map[string]slsaStub { | ||||
| 	if len(r.assets) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	res := make(map[string]slsaStub) | ||||
| 	for p, a := range r.assets { | ||||
| 		if a.slsa == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		res[p] = *a.slsa | ||||
| 	} | ||||
| 	return res | ||||
| } | ||||
|  | ||||
| func (r *result) SBOM() map[string]sbomStub { | ||||
| 	if len(r.assets) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	res := make(map[string]sbomStub) | ||||
| 	for p, a := range r.assets { | ||||
| 		if a.sbom == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		res[p] = *a.sbom | ||||
| 	} | ||||
| 	return res | ||||
| } | ||||
| @@ -6,20 +6,15 @@ import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"text/tabwriter" | ||||
| 	"text/template" | ||||
|  | ||||
| 	"github.com/containerd/containerd/images" | ||||
| 	"github.com/containerd/containerd/platforms" | ||||
| 	"github.com/docker/distribution/reference" | ||||
| 	binfotypes "github.com/moby/buildkit/util/buildinfo/types" | ||||
| 	"github.com/moby/buildkit/util/imageutil" | ||||
| 	"github.com/opencontainers/go-digest" | ||||
| 	ocispecs "github.com/opencontainers/image-spec/specs-go/v1" | ||||
| 	"golang.org/x/sync/errgroup" | ||||
| ) | ||||
|  | ||||
| const defaultPfx = "  " | ||||
| @@ -31,11 +26,10 @@ type Printer struct { | ||||
| 	name   string | ||||
| 	format string | ||||
|  | ||||
| 	raw       []byte | ||||
| 	ref       reference.Named | ||||
| 	manifest  ocispecs.Descriptor | ||||
| 	index     ocispecs.Index | ||||
| 	platforms []ocispecs.Platform | ||||
| 	raw      []byte | ||||
| 	ref      reference.Named | ||||
| 	manifest ocispecs.Descriptor | ||||
| 	index    ocispecs.Index | ||||
| } | ||||
|  | ||||
| func NewPrinter(ctx context.Context, opt Opt, name string, format string) (*Printer, error) { | ||||
| @@ -46,38 +40,25 @@ func NewPrinter(ctx context.Context, opt Opt, name string, format string) (*Prin | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	dt, manifest, err := resolver.Get(ctx, name) | ||||
| 	dt, mfst, err := resolver.Get(ctx, ref.String()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	var index ocispecs.Index | ||||
| 	if err = json.Unmarshal(dt, &index); err != nil { | ||||
| 	var idx ocispecs.Index | ||||
| 	if err = json.Unmarshal(dt, &idx); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	var pforms []ocispecs.Platform | ||||
| 	switch manifest.MediaType { | ||||
| 	case images.MediaTypeDockerSchema2ManifestList, ocispecs.MediaTypeImageIndex: | ||||
| 		for _, m := range index.Manifests { | ||||
| 			if m.Platform != nil { | ||||
| 				pforms = append(pforms, *m.Platform) | ||||
| 			} | ||||
| 		} | ||||
| 	default: | ||||
| 		pforms = append(pforms, platforms.DefaultSpec()) | ||||
| 	} | ||||
|  | ||||
| 	return &Printer{ | ||||
| 		ctx:       ctx, | ||||
| 		resolver:  resolver, | ||||
| 		name:      name, | ||||
| 		format:    format, | ||||
| 		raw:       dt, | ||||
| 		ref:       ref, | ||||
| 		manifest:  manifest, | ||||
| 		index:     index, | ||||
| 		platforms: pforms, | ||||
| 		ctx:      ctx, | ||||
| 		resolver: resolver, | ||||
| 		name:     name, | ||||
| 		format:   format, | ||||
| 		raw:      dt, | ||||
| 		ref:      ref, | ||||
| 		manifest: mfst, | ||||
| 		index:    idx, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| @@ -102,6 +83,11 @@ func (p *Printer) Print(raw bool, out io.Writer) error { | ||||
| 		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, "", "  ") | ||||
| @@ -112,46 +98,17 @@ func (p *Printer) Print(raw bool, out io.Writer) error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	imageconfigs := make(map[string]*ocispecs.Image) | ||||
| 	imageconfigsMutex := sync.Mutex{} | ||||
| 	buildinfos := make(map[string]*binfotypes.BuildInfo) | ||||
| 	buildinfosMutex := sync.Mutex{} | ||||
|  | ||||
| 	eg, _ := errgroup.WithContext(p.ctx) | ||||
| 	for _, platform := range p.platforms { | ||||
| 		func(platform ocispecs.Platform) { | ||||
| 			eg.Go(func() error { | ||||
| 				img, dtic, err := p.getImageConfig(&platform) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} else if img != nil { | ||||
| 					imageconfigsMutex.Lock() | ||||
| 					imageconfigs[platforms.Format(platform)] = img | ||||
| 					imageconfigsMutex.Unlock() | ||||
| 				} | ||||
| 				if bi, err := imageutil.BuildInfo(dtic); err != nil { | ||||
| 					return err | ||||
| 				} else if bi != nil { | ||||
| 					buildinfosMutex.Lock() | ||||
| 					buildinfos[platforms.Format(platform)] = bi | ||||
| 					buildinfosMutex.Unlock() | ||||
| 				} | ||||
| 				return nil | ||||
| 			}) | ||||
| 		}(platform) | ||||
| 	} | ||||
| 	if err := eg.Wait(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	imageconfigs := res.Configs() | ||||
| 	slsas := res.SLSA() | ||||
| 	sboms := res.SBOM() | ||||
| 	format := tpl.Root.String() | ||||
|  | ||||
| 	var manifest interface{} | ||||
| 	var mfst interface{} | ||||
| 	switch p.manifest.MediaType { | ||||
| 	case images.MediaTypeDockerSchema2Manifest, ocispecs.MediaTypeImageManifest: | ||||
| 		manifest = p.manifest | ||||
| 		mfst = p.manifest | ||||
| 	case images.MediaTypeDockerSchema2ManifestList, ocispecs.MediaTypeImageIndex: | ||||
| 		manifest = struct { | ||||
| 		mfst = struct { | ||||
| 			SchemaVersion int                   `json:"schemaVersion"` | ||||
| 			MediaType     string                `json:"mediaType,omitempty"` | ||||
| 			Digest        digest.Digest         `json:"digest"` | ||||
| @@ -170,10 +127,11 @@ func (p *Printer) Print(raw bool, out io.Writer) error { | ||||
|  | ||||
| 	switch { | ||||
| 	// TODO: print formatted config | ||||
| 	case strings.HasPrefix(format, "{{.Manifest"), strings.HasPrefix(format, "{{.BuildInfo"): | ||||
| 	case strings.HasPrefix(format, "{{.Manifest"): | ||||
| 		w := tabwriter.NewWriter(out, 0, 0, 1, ' ', 0) | ||||
| 		_, _ = fmt.Fprintf(w, "Name:\t%s\n", p.ref.String()) | ||||
| 		if strings.HasPrefix(format, "{{.Manifest") { | ||||
| 		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() | ||||
| @@ -181,42 +139,47 @@ func (p *Printer) Print(raw bool, out io.Writer) error { | ||||
| 			case images.MediaTypeDockerSchema2ManifestList, ocispecs.MediaTypeImageIndex: | ||||
| 				_ = p.printManifestList(out) | ||||
| 			} | ||||
| 		} else if strings.HasPrefix(format, "{{.BuildInfo") { | ||||
| 			_ = w.Flush() | ||||
| 			_ = p.printBuildInfos(buildinfos, out) | ||||
| 		} | ||||
| 	default: | ||||
| 		if len(p.platforms) > 1 { | ||||
| 		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"` | ||||
| 				BuildInfo map[string]*binfotypes.BuildInfo `json:"buildinfo,omitempty"` | ||||
| 				Name     string                     `json:"name,omitempty"` | ||||
| 				Manifest interface{}                `json:"manifest,omitempty"` | ||||
| 				Image    map[string]*ocispecs.Image `json:"image,omitempty"` | ||||
| 				SLSA     map[string]slsaStub        `json:"SLSA,omitempty"` | ||||
| 				SBOM     map[string]sbomStub        `json:"SBOM,omitempty"` | ||||
| 			}{ | ||||
| 				Name:      p.name, | ||||
| 				Manifest:  manifest, | ||||
| 				Image:     imageconfigs, | ||||
| 				BuildInfo: buildinfos, | ||||
| 				Name:     p.name, | ||||
| 				Manifest: mfst, | ||||
| 				Image:    imageconfigs, | ||||
| 				SLSA:     slsas, | ||||
| 				SBOM:     sboms, | ||||
| 			}) | ||||
| 		} | ||||
| 		var ic *ocispecs.Image | ||||
| 		for _, v := range imageconfigs { | ||||
| 			ic = v | ||||
| 		} | ||||
| 		var bi *binfotypes.BuildInfo | ||||
| 		for _, v := range buildinfos { | ||||
| 			bi = v | ||||
| 		var slsa slsaStub | ||||
| 		for _, v := range slsas { | ||||
| 			slsa = 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"` | ||||
| 			BuildInfo *binfotypes.BuildInfo `json:"buildinfo,omitempty"` | ||||
| 			Name     string          `json:"name,omitempty"` | ||||
| 			Manifest interface{}     `json:"manifest,omitempty"` | ||||
| 			Image    *ocispecs.Image `json:"image,omitempty"` | ||||
| 			SLSA     slsaStub        `json:"SLSA,omitempty"` | ||||
| 			SBOM     sbomStub        `json:"SBOM,omitempty"` | ||||
| 		}{ | ||||
| 			Name:      p.name, | ||||
| 			Manifest:  manifest, | ||||
| 			Image:     ic, | ||||
| 			BuildInfo: bi, | ||||
| 			Name:     p.name, | ||||
| 			Manifest: mfst, | ||||
| 			Image:    ic, | ||||
| 			SLSA:     slsa, | ||||
| 			SBOM:     sbom, | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| @@ -264,83 +227,3 @@ func (p *Printer) printManifestList(out io.Writer) error { | ||||
| 	} | ||||
| 	return w.Flush() | ||||
| } | ||||
|  | ||||
| func (p *Printer) printBuildInfos(bis map[string]*binfotypes.BuildInfo, out io.Writer) error { | ||||
| 	if len(bis) == 0 { | ||||
| 		return nil | ||||
| 	} else if len(bis) == 1 { | ||||
| 		for _, bi := range bis { | ||||
| 			return p.printBuildInfo(bi, "", out) | ||||
| 		} | ||||
| 	} | ||||
| 	var pkeys []string | ||||
| 	for _, pform := range p.platforms { | ||||
| 		pkeys = append(pkeys, platforms.Format(pform)) | ||||
| 	} | ||||
| 	sort.Strings(pkeys) | ||||
| 	for _, platform := range pkeys { | ||||
| 		bi := bis[platform] | ||||
| 		w := tabwriter.NewWriter(out, 0, 0, 1, ' ', 0) | ||||
| 		_, _ = fmt.Fprintf(w, "\t\nPlatform:\t%s\t\n", platform) | ||||
| 		_ = w.Flush() | ||||
| 		if err := p.printBuildInfo(bi, "", out); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (p *Printer) printBuildInfo(bi *binfotypes.BuildInfo, pfx string, out io.Writer) error { | ||||
| 	w := tabwriter.NewWriter(out, 0, 0, 1, ' ', 0) | ||||
| 	_, _ = fmt.Fprintf(w, "%sFrontend:\t%s\n", pfx, bi.Frontend) | ||||
|  | ||||
| 	if len(bi.Attrs) > 0 { | ||||
| 		_, _ = fmt.Fprintf(w, "%sAttrs:\t\n", pfx) | ||||
| 		_ = w.Flush() | ||||
| 		for k, v := range bi.Attrs { | ||||
| 			_, _ = fmt.Fprintf(w, "%s%s:\t%s\n", pfx+defaultPfx, k, *v) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(bi.Sources) > 0 { | ||||
| 		_, _ = fmt.Fprintf(w, "%sSources:\t\n", pfx) | ||||
| 		_ = w.Flush() | ||||
| 		for i, v := range bi.Sources { | ||||
| 			if i != 0 { | ||||
| 				_, _ = fmt.Fprintf(w, "\t\n") | ||||
| 			} | ||||
| 			_, _ = fmt.Fprintf(w, "%sType:\t%s\n", pfx+defaultPfx, v.Type) | ||||
| 			_, _ = fmt.Fprintf(w, "%sRef:\t%s\n", pfx+defaultPfx, v.Ref) | ||||
| 			_, _ = fmt.Fprintf(w, "%sPin:\t%s\n", pfx+defaultPfx, v.Pin) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(bi.Deps) > 0 { | ||||
| 		_, _ = fmt.Fprintf(w, "%sDeps:\t\n", pfx) | ||||
| 		_ = w.Flush() | ||||
| 		firstPass := true | ||||
| 		for k, v := range bi.Deps { | ||||
| 			if !firstPass { | ||||
| 				_, _ = fmt.Fprintf(w, "\t\n") | ||||
| 			} | ||||
| 			_, _ = fmt.Fprintf(w, "%sName:\t%s\n", pfx+defaultPfx, k) | ||||
| 			_ = w.Flush() | ||||
| 			_ = p.printBuildInfo(&v, pfx+defaultPfx, out) | ||||
| 			firstPass = false | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return w.Flush() | ||||
| } | ||||
|  | ||||
| func (p *Printer) getImageConfig(platform *ocispecs.Platform) (*ocispecs.Image, []byte, error) { | ||||
| 	_, dtic, err := p.resolver.ImageConfig(p.ctx, p.name, platform) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	var img *ocispecs.Image | ||||
| 	if err = json.Unmarshal(dtic, &img); err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	return img, dtic, nil | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 CrazyMax
					CrazyMax