github.com/pdmccormick/importable-docker-buildx@v0.0.0-20240426161518-e47091289030/util/imagetools/printers.go (about)

     1  package imagetools
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"strings"
    10  	"text/tabwriter"
    11  	"text/template"
    12  
    13  	"github.com/containerd/containerd/images"
    14  	"github.com/containerd/containerd/platforms"
    15  	"github.com/distribution/reference"
    16  	"github.com/opencontainers/go-digest"
    17  	ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
    18  )
    19  
    20  const defaultPfx = "  "
    21  
    22  type Printer struct {
    23  	ctx      context.Context
    24  	resolver *Resolver
    25  
    26  	name   string
    27  	format string
    28  
    29  	raw      []byte
    30  	ref      reference.Named
    31  	manifest ocispecs.Descriptor
    32  	index    ocispecs.Index
    33  }
    34  
    35  func NewPrinter(ctx context.Context, opt Opt, name string, format string) (*Printer, error) {
    36  	resolver := New(opt)
    37  
    38  	ref, err := parseRef(name)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  
    43  	dt, mfst, err := resolver.Get(ctx, ref.String())
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  
    48  	var idx ocispecs.Index
    49  	if err = json.Unmarshal(dt, &idx); err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	return &Printer{
    54  		ctx:      ctx,
    55  		resolver: resolver,
    56  		name:     name,
    57  		format:   format,
    58  		raw:      dt,
    59  		ref:      ref,
    60  		manifest: mfst,
    61  		index:    idx,
    62  	}, nil
    63  }
    64  
    65  func (p *Printer) Print(raw bool, out io.Writer) error {
    66  	if raw {
    67  		_, err := fmt.Fprintf(out, "%s", p.raw) // avoid newline to keep digest
    68  		return err
    69  	}
    70  
    71  	if p.format == "" {
    72  		w := tabwriter.NewWriter(out, 0, 0, 1, ' ', 0)
    73  		_, _ = fmt.Fprintf(w, "Name:\t%s\n", p.ref.String())
    74  		_, _ = fmt.Fprintf(w, "MediaType:\t%s\n", p.manifest.MediaType)
    75  		_, _ = fmt.Fprintf(w, "Digest:\t%s\n", p.manifest.Digest)
    76  		_ = w.Flush()
    77  		switch p.manifest.MediaType {
    78  		case images.MediaTypeDockerSchema2ManifestList, ocispecs.MediaTypeImageIndex:
    79  			if err := p.printManifestList(out); err != nil {
    80  				return err
    81  			}
    82  		}
    83  		return nil
    84  	}
    85  
    86  	res, err := newLoader(p.resolver.resolver()).Load(p.ctx, p.name)
    87  	if err != nil {
    88  		return err
    89  	}
    90  
    91  	tpl, err := template.New("").Funcs(template.FuncMap{
    92  		"json": func(v interface{}) string {
    93  			b, _ := json.MarshalIndent(v, "", "  ")
    94  			return string(b)
    95  		},
    96  	}).Parse(p.format)
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	imageconfigs := res.Configs()
   102  	format := tpl.Root.String()
   103  
   104  	var mfst interface{}
   105  	switch p.manifest.MediaType {
   106  	case images.MediaTypeDockerSchema2Manifest, ocispecs.MediaTypeImageManifest:
   107  		mfst = p.manifest
   108  	case images.MediaTypeDockerSchema2ManifestList, ocispecs.MediaTypeImageIndex:
   109  		mfst = struct {
   110  			SchemaVersion int                   `json:"schemaVersion"`
   111  			MediaType     string                `json:"mediaType,omitempty"`
   112  			Digest        digest.Digest         `json:"digest"`
   113  			Size          int64                 `json:"size"`
   114  			Manifests     []ocispecs.Descriptor `json:"manifests"`
   115  			Annotations   map[string]string     `json:"annotations,omitempty"`
   116  		}{
   117  			SchemaVersion: p.index.Versioned.SchemaVersion,
   118  			MediaType:     p.index.MediaType,
   119  			Digest:        p.manifest.Digest,
   120  			Size:          p.manifest.Size,
   121  			Manifests:     p.index.Manifests,
   122  			Annotations:   p.index.Annotations,
   123  		}
   124  	}
   125  
   126  	switch {
   127  	// TODO: print formatted config
   128  	case strings.HasPrefix(format, "{{.Manifest"):
   129  		w := tabwriter.NewWriter(out, 0, 0, 1, ' ', 0)
   130  		_, _ = fmt.Fprintf(w, "Name:\t%s\n", p.ref.String())
   131  		switch {
   132  		case strings.HasPrefix(format, "{{.Manifest"):
   133  			_, _ = fmt.Fprintf(w, "MediaType:\t%s\n", p.manifest.MediaType)
   134  			_, _ = fmt.Fprintf(w, "Digest:\t%s\n", p.manifest.Digest)
   135  			_ = w.Flush()
   136  			switch p.manifest.MediaType {
   137  			case images.MediaTypeDockerSchema2ManifestList, ocispecs.MediaTypeImageIndex:
   138  				_ = p.printManifestList(out)
   139  			}
   140  		}
   141  	default:
   142  		if len(res.platforms) > 1 {
   143  			return tpl.Execute(out, tplInputs{
   144  				Name:     p.name,
   145  				Manifest: mfst,
   146  				Image:    imageconfigs,
   147  				result:   res,
   148  			})
   149  		}
   150  		var ic *ocispecs.Image
   151  		for _, v := range imageconfigs {
   152  			ic = v
   153  		}
   154  		return tpl.Execute(out, tplInput{
   155  			Name:     p.name,
   156  			Manifest: mfst,
   157  			Image:    ic,
   158  			result:   res,
   159  		})
   160  	}
   161  
   162  	return nil
   163  }
   164  
   165  func (p *Printer) printManifestList(out io.Writer) error {
   166  	w := tabwriter.NewWriter(out, 0, 0, 1, ' ', 0)
   167  	_, _ = fmt.Fprintf(w, "\t\n")
   168  	_, _ = fmt.Fprintf(w, "Manifests:\t\n")
   169  	_ = w.Flush()
   170  
   171  	w = tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
   172  	for i, m := range p.index.Manifests {
   173  		if i != 0 {
   174  			_, _ = fmt.Fprintf(w, "\t\n")
   175  		}
   176  		cr, err := reference.WithDigest(p.ref, m.Digest)
   177  		if err != nil {
   178  			return err
   179  		}
   180  		_, _ = fmt.Fprintf(w, "%sName:\t%s\n", defaultPfx, cr.String())
   181  		_, _ = fmt.Fprintf(w, "%sMediaType:\t%s\n", defaultPfx, m.MediaType)
   182  		if p := m.Platform; p != nil {
   183  			_, _ = fmt.Fprintf(w, "%sPlatform:\t%s\n", defaultPfx, platforms.Format(*p))
   184  			if p.OSVersion != "" {
   185  				_, _ = fmt.Fprintf(w, "%sOSVersion:\t%s\n", defaultPfx, p.OSVersion)
   186  			}
   187  			if len(p.OSFeatures) > 0 {
   188  				_, _ = fmt.Fprintf(w, "%sOSFeatures:\t%s\n", defaultPfx, strings.Join(p.OSFeatures, ", "))
   189  			}
   190  			if len(m.URLs) > 0 {
   191  				_, _ = fmt.Fprintf(w, "%sURLs:\t%s\n", defaultPfx, strings.Join(m.URLs, ", "))
   192  			}
   193  			if len(m.Annotations) > 0 {
   194  				_, _ = fmt.Fprintf(w, "%sAnnotations:\t\n", defaultPfx)
   195  				_ = w.Flush()
   196  				w2 := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
   197  				for k, v := range m.Annotations {
   198  					_, _ = fmt.Fprintf(w2, "%s%s:\t%s\n", defaultPfx+defaultPfx, k, v)
   199  				}
   200  				_ = w2.Flush()
   201  			}
   202  		}
   203  	}
   204  	return w.Flush()
   205  }
   206  
   207  type tplInput struct {
   208  	Name     string          `json:"name,omitempty"`
   209  	Manifest interface{}     `json:"manifest,omitempty"`
   210  	Image    *ocispecs.Image `json:"image,omitempty"`
   211  
   212  	result *result
   213  }
   214  
   215  func (inp tplInput) SBOM() (sbomStub, error) {
   216  	sbom, err := inp.result.SBOM()
   217  	if err != nil {
   218  		return sbomStub{}, nil
   219  	}
   220  	for _, v := range sbom {
   221  		return v, nil
   222  	}
   223  	return sbomStub{}, nil
   224  }
   225  
   226  func (inp tplInput) Provenance() (provenanceStub, error) {
   227  	provenance, err := inp.result.Provenance()
   228  	if err != nil {
   229  		return provenanceStub{}, nil
   230  	}
   231  	for _, v := range provenance {
   232  		return v, nil
   233  	}
   234  	return provenanceStub{}, nil
   235  }
   236  
   237  type tplInputs struct {
   238  	Name     string                     `json:"name,omitempty"`
   239  	Manifest interface{}                `json:"manifest,omitempty"`
   240  	Image    map[string]*ocispecs.Image `json:"image,omitempty"`
   241  
   242  	result *result
   243  }
   244  
   245  func (inp tplInputs) SBOM() (map[string]sbomStub, error) {
   246  	return inp.result.SBOM()
   247  }
   248  
   249  func (inp tplInputs) Provenance() (map[string]provenanceStub, error) {
   250  	return inp.result.Provenance()
   251  }