mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-01 00:23:56 +08:00 
			
		
		
		
	imagetools inspect: add --format flag
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
		| @@ -14,7 +14,10 @@ import ( | ||||
| 	"github.com/docker/buildx/util/resolver" | ||||
| 	clitypes "github.com/docker/cli/cli/config/types" | ||||
| 	"github.com/docker/distribution/reference" | ||||
| 	"github.com/moby/buildkit/util/contentutil" | ||||
| 	"github.com/moby/buildkit/util/imageutil" | ||||
| 	"github.com/moby/buildkit/util/tracing" | ||||
| 	"github.com/opencontainers/go-digest" | ||||
| 	ocispec "github.com/opencontainers/image-spec/specs-go/v1" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| ) | ||||
| @@ -29,14 +32,16 @@ type Opt struct { | ||||
| } | ||||
|  | ||||
| type Resolver struct { | ||||
| 	auth  docker.Authorizer | ||||
| 	hosts docker.RegistryHosts | ||||
| 	auth   docker.Authorizer | ||||
| 	hosts  docker.RegistryHosts | ||||
| 	buffer contentutil.Buffer | ||||
| } | ||||
|  | ||||
| func New(opt Opt) *Resolver { | ||||
| 	return &Resolver{ | ||||
| 		auth:  docker.NewDockerAuthorizer(docker.WithAuthCreds(toCredentialsFunc(opt.Auth)), docker.WithAuthClient(http.DefaultClient)), | ||||
| 		hosts: resolver.NewRegistryConfig(opt.RegistryConfig), | ||||
| 		auth:   docker.NewDockerAuthorizer(docker.WithAuthCreds(toCredentialsFunc(opt.Auth)), docker.WithAuthClient(http.DefaultClient)), | ||||
| 		hosts:  resolver.NewRegistryConfig(opt.RegistryConfig), | ||||
| 		buffer: contentutil.NewBuffer(), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -157,3 +162,11 @@ func RegistryAuthForRef(ref string, a Auth) (string, error) { | ||||
| 	} | ||||
| 	return base64.URLEncoding.EncodeToString(buf), nil | ||||
| } | ||||
|  | ||||
| func (r *Resolver) ImageConfig(ctx context.Context, in string) (digest.Digest, []byte, error) { | ||||
| 	in, _, err := r.Resolve(ctx, in) | ||||
| 	if err != nil { | ||||
| 		return "", nil, err | ||||
| 	} | ||||
| 	return imageutil.Config(ctx, in, r.resolver(), r.buffer, nil, nil) | ||||
| } | ||||
|   | ||||
| @@ -1,74 +1,242 @@ | ||||
| package imagetools | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/base64" | ||||
| 	"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" | ||||
| 	ocispec "github.com/opencontainers/image-spec/specs-go/v1" | ||||
| 	binfotypes "github.com/moby/buildkit/util/buildinfo/types" | ||||
| 	ocispecs "github.com/opencontainers/image-spec/specs-go/v1" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
|  | ||||
| func PrintManifestList(dt []byte, desc ocispec.Descriptor, refstr string, out io.Writer) error { | ||||
| 	ref, err := parseRef(refstr) | ||||
| 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 | ||||
| 	image    ocispecs.Image | ||||
| 	binfo    binfotypes.BuildInfo | ||||
| } | ||||
|  | ||||
| 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, manifest, err := resolver.Get(ctx, name) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	var index ocispecs.Index | ||||
| 	if err = json.Unmarshal(dt, &index); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	_, dtcic, err := resolver.ImageConfig(ctx, name) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	var image ocispecs.Image | ||||
| 	if err = json.Unmarshal(dtcic, &image); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	var binfo binfotypes.BuildInfo | ||||
| 	if len(dtcic) > 0 { | ||||
| 		var biconfig binfotypes.ImageConfig | ||||
| 		if err := json.Unmarshal(dtcic, &biconfig); err != nil { | ||||
| 			return nil, errors.Wrap(err, "failed to unmarshal image config") | ||||
| 		} | ||||
| 		if len(biconfig.BuildInfo) > 0 { | ||||
| 			bidec, err := base64.StdEncoding.DecodeString(biconfig.BuildInfo) | ||||
| 			if err != nil { | ||||
| 				return nil, errors.Wrap(err, "failed to decode build info") | ||||
| 			} | ||||
| 			if err = json.Unmarshal(bidec, &binfo); err != nil { | ||||
| 				return nil, errors.Wrap(err, "failed to unmarshal build info") | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &Printer{ | ||||
| 		ctx:      ctx, | ||||
| 		resolver: resolver, | ||||
| 		name:     name, | ||||
| 		format:   format, | ||||
| 		raw:      dt, | ||||
| 		ref:      ref, | ||||
| 		manifest: manifest, | ||||
| 		index:    index, | ||||
| 		image:    image, | ||||
| 		binfo:    binfo, | ||||
| 	}, 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 | ||||
| 	} | ||||
|  | ||||
| 	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 | ||||
| 	} | ||||
|  | ||||
| 	var mfst ocispec.Index | ||||
| 	if err := json.Unmarshal(dt, &mfst); err != nil { | ||||
| 		return err | ||||
| 	format := tpl.Root.String() | ||||
|  | ||||
| 	var manifest interface{} | ||||
| 	switch p.manifest.MediaType { | ||||
| 	case images.MediaTypeDockerSchema2Manifest, ocispecs.MediaTypeImageManifest: | ||||
| 		manifest = p.manifest | ||||
| 	case images.MediaTypeDockerSchema2ManifestList, ocispecs.MediaTypeImageIndex: | ||||
| 		manifest = p.index | ||||
| 	} | ||||
| 	v := struct { | ||||
| 		Name      string               `json:"name,omitempty"` | ||||
| 		Manifest  interface{}          `json:"manifest,omitempty"` | ||||
| 		Config    ocispecs.Image       `json:"config,omitempty"` | ||||
| 		BuildInfo binfotypes.BuildInfo `json:"buildinfo,omitempty"` | ||||
| 	}{ | ||||
| 		Name:      p.name, | ||||
| 		Manifest:  manifest, | ||||
| 		Config:    p.image, | ||||
| 		BuildInfo: p.binfo, | ||||
| 	} | ||||
|  | ||||
| 	switch { | ||||
| 	case strings.HasPrefix(format, "{{.Manifest"), strings.HasPrefix(format, "{{.Config"), strings.HasPrefix(format, "{{.BuildInfo"): | ||||
| 		w := tabwriter.NewWriter(out, 0, 0, 1, ' ', 0) | ||||
| 		_, _ = fmt.Fprintf(w, "Name:\t%s\n", p.ref.String()) | ||||
| 		_ = w.Flush() | ||||
| 		if 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) | ||||
| 			} | ||||
| 		} else if strings.HasPrefix(format, "{{.Config") { | ||||
| 			// TODO: print formatted config | ||||
| 		} else if strings.HasPrefix(format, "{{.BuildInfo") { | ||||
| 			_ = p.printBuildInfo(p.binfo, "", out) | ||||
| 		} | ||||
| 	default: | ||||
| 		return tpl.Execute(out, v) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (p *Printer) printManifestList(out io.Writer) error { | ||||
| 	w := tabwriter.NewWriter(out, 0, 0, 1, ' ', 0) | ||||
|  | ||||
| 	fmt.Fprintf(w, "Name:\t%s\n", ref.String()) | ||||
| 	fmt.Fprintf(w, "MediaType:\t%s\n", desc.MediaType) | ||||
| 	fmt.Fprintf(w, "Digest:\t%s\n", desc.Digest) | ||||
| 	fmt.Fprintf(w, "\t\n") | ||||
|  | ||||
| 	fmt.Fprintf(w, "Manifests:\t\n") | ||||
| 	w.Flush() | ||||
|  | ||||
| 	pfx := "  " | ||||
| 	_, _ = 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 mfst.Manifests { | ||||
| 	for i, m := range p.index.Manifests { | ||||
| 		if i != 0 { | ||||
| 			fmt.Fprintf(w, "\t\n") | ||||
| 			_, _ = fmt.Fprintf(w, "\t\n") | ||||
| 		} | ||||
| 		cr, err := reference.WithDigest(ref, m.Digest) | ||||
| 		cr, err := reference.WithDigest(p.ref, m.Digest) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		fmt.Fprintf(w, "%sName:\t%s\n", pfx, cr.String()) | ||||
| 		fmt.Fprintf(w, "%sMediaType:\t%s\n", pfx, m.MediaType) | ||||
| 		_, _ = 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", pfx, platforms.Format(*p)) | ||||
| 			_, _ = fmt.Fprintf(w, "%sPlatform:\t%s\n", defaultPfx, platforms.Format(*p)) | ||||
| 			if p.OSVersion != "" { | ||||
| 				fmt.Fprintf(w, "%sOSVersion:\t%s\n", pfx, p.OSVersion) | ||||
| 				_, _ = fmt.Fprintf(w, "%sOSVersion:\t%s\n", defaultPfx, p.OSVersion) | ||||
| 			} | ||||
| 			if len(p.OSFeatures) > 0 { | ||||
| 				fmt.Fprintf(w, "%sOSFeatures:\t%s\n", pfx, strings.Join(p.OSFeatures, ", ")) | ||||
| 				_, _ = fmt.Fprintf(w, "%sOSFeatures:\t%s\n", defaultPfx, strings.Join(p.OSFeatures, ", ")) | ||||
| 			} | ||||
| 			if len(m.URLs) > 0 { | ||||
| 				fmt.Fprintf(w, "%sURLs:\t%s\n", pfx, strings.Join(m.URLs, ", ")) | ||||
| 				_, _ = fmt.Fprintf(w, "%sURLs:\t%s\n", defaultPfx, strings.Join(m.URLs, ", ")) | ||||
| 			} | ||||
| 			if len(m.Annotations) > 0 { | ||||
| 				w.Flush() | ||||
| 				_ = w.Flush() | ||||
| 				w2 := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) | ||||
| 				pfx2 := pfx + "  " | ||||
| 				for k, v := range m.Annotations { | ||||
| 					fmt.Fprintf(w2, "%s%s:\t%s\n", pfx2, k, v) | ||||
| 					_, _ = fmt.Fprintf(w2, "%s%s:\t%s\n", defaultPfx+defaultPfx, k, v) | ||||
| 				} | ||||
| 				w2.Flush() | ||||
| 				_ = w2.Flush() | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return w.Flush() | ||||
| } | ||||
|  | ||||
| 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() | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 CrazyMax
					CrazyMax