You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
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()
|
|
}
|