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 }