123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- package formatter
- import (
- "bytes"
- "fmt"
- "io"
- "strings"
- "text/tabwriter"
- "text/template"
- "github.com/docker/docker/reference"
- "github.com/docker/docker/utils/templates"
- "github.com/docker/engine-api/types"
- )
- const (
- tableFormatKey = "table"
- rawFormatKey = "raw"
- defaultContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.RunningFor}} ago\t{{.Status}}\t{{.Ports}}\t{{.Names}}"
- defaultImageTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.Size}}"
- defaultImageTableFormatWithDigest = "table {{.Repository}}\t{{.Tag}}\t{{.Digest}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.Size}}"
- defaultQuietFormat = "{{.ID}}"
- )
- // Context contains information required by the formatter to print the output as desired.
- type Context struct {
- // Output is the output stream to which the formatted string is written.
- Output io.Writer
- // Format is used to choose raw, table or custom format for the output.
- Format string
- // Quiet when set to true will simply print minimal information.
- Quiet bool
- // Trunc when set to true will truncate the output of certain fields such as Container ID.
- Trunc bool
- // internal element
- table bool
- finalFormat string
- header string
- buffer *bytes.Buffer
- }
- func (c *Context) preformat() {
- c.finalFormat = c.Format
- if strings.HasPrefix(c.Format, tableKey) {
- c.table = true
- c.finalFormat = c.finalFormat[len(tableKey):]
- }
- c.finalFormat = strings.Trim(c.finalFormat, " ")
- r := strings.NewReplacer(`\t`, "\t", `\n`, "\n")
- c.finalFormat = r.Replace(c.finalFormat)
- }
- func (c *Context) parseFormat() (*template.Template, error) {
- tmpl, err := templates.Parse(c.finalFormat)
- if err != nil {
- c.buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err))
- c.buffer.WriteTo(c.Output)
- }
- return tmpl, err
- }
- func (c *Context) postformat(tmpl *template.Template, subContext subContext) {
- if c.table {
- if len(c.header) == 0 {
- // if we still don't have a header, we didn't have any containers so we need to fake it to get the right headers from the template
- tmpl.Execute(bytes.NewBufferString(""), subContext)
- c.header = subContext.fullHeader()
- }
- t := tabwriter.NewWriter(c.Output, 20, 1, 3, ' ', 0)
- t.Write([]byte(c.header))
- t.Write([]byte("\n"))
- c.buffer.WriteTo(t)
- t.Flush()
- } else {
- c.buffer.WriteTo(c.Output)
- }
- }
- func (c *Context) contextFormat(tmpl *template.Template, subContext subContext) error {
- if err := tmpl.Execute(c.buffer, subContext); err != nil {
- c.buffer = bytes.NewBufferString(fmt.Sprintf("Template parsing error: %v\n", err))
- c.buffer.WriteTo(c.Output)
- return err
- }
- if c.table && len(c.header) == 0 {
- c.header = subContext.fullHeader()
- }
- c.buffer.WriteString("\n")
- return nil
- }
- // ContainerContext contains container specific information required by the formater, encapsulate a Context struct.
- type ContainerContext struct {
- Context
- // Size when set to true will display the size of the output.
- Size bool
- // Containers
- Containers []types.Container
- }
- // ImageContext contains image specific information required by the formater, encapsulate a Context struct.
- type ImageContext struct {
- Context
- Digest bool
- // Images
- Images []types.Image
- }
- func (ctx ContainerContext) Write() {
- switch ctx.Format {
- case tableFormatKey:
- if ctx.Quiet {
- ctx.Format = defaultQuietFormat
- } else {
- ctx.Format = defaultContainerTableFormat
- if ctx.Size {
- ctx.Format += `\t{{.Size}}`
- }
- }
- case rawFormatKey:
- if ctx.Quiet {
- ctx.Format = `container_id: {{.ID}}`
- } else {
- ctx.Format = `container_id: {{.ID}}\nimage: {{.Image}}\ncommand: {{.Command}}\ncreated_at: {{.CreatedAt}}\nstatus: {{.Status}}\nnames: {{.Names}}\nlabels: {{.Labels}}\nports: {{.Ports}}\n`
- if ctx.Size {
- ctx.Format += `size: {{.Size}}\n`
- }
- }
- }
- ctx.buffer = bytes.NewBufferString("")
- ctx.preformat()
- tmpl, err := ctx.parseFormat()
- if err != nil {
- return
- }
- for _, container := range ctx.Containers {
- containerCtx := &containerContext{
- trunc: ctx.Trunc,
- c: container,
- }
- err = ctx.contextFormat(tmpl, containerCtx)
- if err != nil {
- return
- }
- }
- ctx.postformat(tmpl, &containerContext{})
- }
- func isDangling(image types.Image) bool {
- return len(image.RepoTags) == 1 && image.RepoTags[0] == "<none>:<none>" && len(image.RepoDigests) == 1 && image.RepoDigests[0] == "<none>@<none>"
- }
- func (ctx ImageContext) Write() {
- switch ctx.Format {
- case tableFormatKey:
- ctx.Format = defaultImageTableFormat
- if ctx.Digest {
- ctx.Format = defaultImageTableFormatWithDigest
- }
- if ctx.Quiet {
- ctx.Format = defaultQuietFormat
- }
- case rawFormatKey:
- if ctx.Quiet {
- ctx.Format = `image_id: {{.ID}}`
- } else {
- if ctx.Digest {
- ctx.Format = `repository: {{ .Repository }}
- tag: {{.Tag}}
- digest: {{.Digest}}
- image_id: {{.ID}}
- created_at: {{.CreatedAt}}
- virtual_size: {{.Size}}
- `
- } else {
- ctx.Format = `repository: {{ .Repository }}
- tag: {{.Tag}}
- image_id: {{.ID}}
- created_at: {{.CreatedAt}}
- virtual_size: {{.Size}}
- `
- }
- }
- }
- ctx.buffer = bytes.NewBufferString("")
- ctx.preformat()
- if ctx.table && ctx.Digest && !strings.Contains(ctx.Format, "{{.Digest}}") {
- ctx.finalFormat += "\t{{.Digest}}"
- }
- tmpl, err := ctx.parseFormat()
- if err != nil {
- return
- }
- for _, image := range ctx.Images {
- images := []*imageContext{}
- if isDangling(image) {
- images = append(images, &imageContext{
- trunc: ctx.Trunc,
- i: image,
- repo: "<none>",
- tag: "<none>",
- digest: "<none>",
- })
- } else {
- repoTags := map[string][]string{}
- repoDigests := map[string][]string{}
- for _, refString := range append(image.RepoTags) {
- ref, err := reference.ParseNamed(refString)
- if err != nil {
- continue
- }
- if nt, ok := ref.(reference.NamedTagged); ok {
- repoTags[ref.Name()] = append(repoTags[ref.Name()], nt.Tag())
- }
- }
- for _, refString := range append(image.RepoDigests) {
- ref, err := reference.ParseNamed(refString)
- if err != nil {
- continue
- }
- if c, ok := ref.(reference.Canonical); ok {
- repoDigests[ref.Name()] = append(repoDigests[ref.Name()], c.Digest().String())
- }
- }
- for repo, tags := range repoTags {
- digests := repoDigests[repo]
- // Do not display digests as their own row
- delete(repoDigests, repo)
- if !ctx.Digest {
- // Ignore digest references, just show tag once
- digests = nil
- }
- for _, tag := range tags {
- if len(digests) == 0 {
- images = append(images, &imageContext{
- trunc: ctx.Trunc,
- i: image,
- repo: repo,
- tag: tag,
- digest: "<none>",
- })
- continue
- }
- // Display the digests for each tag
- for _, dgst := range digests {
- images = append(images, &imageContext{
- trunc: ctx.Trunc,
- i: image,
- repo: repo,
- tag: tag,
- digest: dgst,
- })
- }
- }
- }
- // Show rows for remaining digest only references
- for repo, digests := range repoDigests {
- // If digests are displayed, show row per digest
- if ctx.Digest {
- for _, dgst := range digests {
- images = append(images, &imageContext{
- trunc: ctx.Trunc,
- i: image,
- repo: repo,
- tag: "<none>",
- digest: dgst,
- })
- }
- } else {
- images = append(images, &imageContext{
- trunc: ctx.Trunc,
- i: image,
- repo: repo,
- tag: "<none>",
- })
- }
- }
- }
- for _, imageCtx := range images {
- err = ctx.contextFormat(tmpl, imageCtx)
- if err != nil {
- return
- }
- }
- }
- ctx.postformat(tmpl, &imageContext{})
- }
|