github.com/portworx/docker@v1.12.1/api/client/formatter/formatter.go (about)

     1  package formatter
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"strings"
     8  	"text/tabwriter"
     9  	"text/template"
    10  
    11  	"github.com/docker/docker/reference"
    12  	"github.com/docker/docker/utils/templates"
    13  	"github.com/docker/engine-api/types"
    14  )
    15  
    16  const (
    17  	tableFormatKey = "table"
    18  	rawFormatKey   = "raw"
    19  
    20  	defaultContainerTableFormat       = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.RunningFor}} ago\t{{.Status}}\t{{.Ports}}\t{{.Names}}"
    21  	defaultImageTableFormat           = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.Size}}"
    22  	defaultImageTableFormatWithDigest = "table {{.Repository}}\t{{.Tag}}\t{{.Digest}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.Size}}"
    23  	defaultQuietFormat                = "{{.ID}}"
    24  )
    25  
    26  // Context contains information required by the formatter to print the output as desired.
    27  type Context struct {
    28  	// Output is the output stream to which the formatted string is written.
    29  	Output io.Writer
    30  	// Format is used to choose raw, table or custom format for the output.
    31  	Format string
    32  	// Quiet when set to true will simply print minimal information.
    33  	Quiet bool
    34  	// Trunc when set to true will truncate the output of certain fields such as Container ID.
    35  	Trunc bool
    36  
    37  	// internal element
    38  	table       bool
    39  	finalFormat string
    40  	header      string
    41  	buffer      *bytes.Buffer
    42  }
    43  
    44  func (c *Context) preformat() {
    45  	c.finalFormat = c.Format
    46  
    47  	if strings.HasPrefix(c.Format, tableKey) {
    48  		c.table = true
    49  		c.finalFormat = c.finalFormat[len(tableKey):]
    50  	}
    51  
    52  	c.finalFormat = strings.Trim(c.finalFormat, " ")
    53  	r := strings.NewReplacer(`\t`, "\t", `\n`, "\n")
    54  	c.finalFormat = r.Replace(c.finalFormat)
    55  }
    56  
    57  func (c *Context) parseFormat() (*template.Template, error) {
    58  	tmpl, err := templates.Parse(c.finalFormat)
    59  	if err != nil {
    60  		c.buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err))
    61  		c.buffer.WriteTo(c.Output)
    62  	}
    63  	return tmpl, err
    64  }
    65  
    66  func (c *Context) postformat(tmpl *template.Template, subContext subContext) {
    67  	if c.table {
    68  		if len(c.header) == 0 {
    69  			// 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
    70  			tmpl.Execute(bytes.NewBufferString(""), subContext)
    71  			c.header = subContext.fullHeader()
    72  		}
    73  
    74  		t := tabwriter.NewWriter(c.Output, 20, 1, 3, ' ', 0)
    75  		t.Write([]byte(c.header))
    76  		t.Write([]byte("\n"))
    77  		c.buffer.WriteTo(t)
    78  		t.Flush()
    79  	} else {
    80  		c.buffer.WriteTo(c.Output)
    81  	}
    82  }
    83  
    84  func (c *Context) contextFormat(tmpl *template.Template, subContext subContext) error {
    85  	if err := tmpl.Execute(c.buffer, subContext); err != nil {
    86  		c.buffer = bytes.NewBufferString(fmt.Sprintf("Template parsing error: %v\n", err))
    87  		c.buffer.WriteTo(c.Output)
    88  		return err
    89  	}
    90  	if c.table && len(c.header) == 0 {
    91  		c.header = subContext.fullHeader()
    92  	}
    93  	c.buffer.WriteString("\n")
    94  	return nil
    95  }
    96  
    97  // ContainerContext contains container specific information required by the formater, encapsulate a Context struct.
    98  type ContainerContext struct {
    99  	Context
   100  	// Size when set to true will display the size of the output.
   101  	Size bool
   102  	// Containers
   103  	Containers []types.Container
   104  }
   105  
   106  // ImageContext contains image specific information required by the formater, encapsulate a Context struct.
   107  type ImageContext struct {
   108  	Context
   109  	Digest bool
   110  	// Images
   111  	Images []types.Image
   112  }
   113  
   114  func (ctx ContainerContext) Write() {
   115  	switch ctx.Format {
   116  	case tableFormatKey:
   117  		if ctx.Quiet {
   118  			ctx.Format = defaultQuietFormat
   119  		} else {
   120  			ctx.Format = defaultContainerTableFormat
   121  			if ctx.Size {
   122  				ctx.Format += `\t{{.Size}}`
   123  			}
   124  		}
   125  	case rawFormatKey:
   126  		if ctx.Quiet {
   127  			ctx.Format = `container_id: {{.ID}}`
   128  		} else {
   129  			ctx.Format = `container_id: {{.ID}}\nimage: {{.Image}}\ncommand: {{.Command}}\ncreated_at: {{.CreatedAt}}\nstatus: {{.Status}}\nnames: {{.Names}}\nlabels: {{.Labels}}\nports: {{.Ports}}\n`
   130  			if ctx.Size {
   131  				ctx.Format += `size: {{.Size}}\n`
   132  			}
   133  		}
   134  	}
   135  
   136  	ctx.buffer = bytes.NewBufferString("")
   137  	ctx.preformat()
   138  
   139  	tmpl, err := ctx.parseFormat()
   140  	if err != nil {
   141  		return
   142  	}
   143  
   144  	for _, container := range ctx.Containers {
   145  		containerCtx := &containerContext{
   146  			trunc: ctx.Trunc,
   147  			c:     container,
   148  		}
   149  		err = ctx.contextFormat(tmpl, containerCtx)
   150  		if err != nil {
   151  			return
   152  		}
   153  	}
   154  
   155  	ctx.postformat(tmpl, &containerContext{})
   156  }
   157  
   158  func isDangling(image types.Image) bool {
   159  	return len(image.RepoTags) == 1 && image.RepoTags[0] == "<none>:<none>" && len(image.RepoDigests) == 1 && image.RepoDigests[0] == "<none>@<none>"
   160  }
   161  
   162  func (ctx ImageContext) Write() {
   163  	switch ctx.Format {
   164  	case tableFormatKey:
   165  		ctx.Format = defaultImageTableFormat
   166  		if ctx.Digest {
   167  			ctx.Format = defaultImageTableFormatWithDigest
   168  		}
   169  		if ctx.Quiet {
   170  			ctx.Format = defaultQuietFormat
   171  		}
   172  	case rawFormatKey:
   173  		if ctx.Quiet {
   174  			ctx.Format = `image_id: {{.ID}}`
   175  		} else {
   176  			if ctx.Digest {
   177  				ctx.Format = `repository: {{ .Repository }}
   178  tag: {{.Tag}}
   179  digest: {{.Digest}}
   180  image_id: {{.ID}}
   181  created_at: {{.CreatedAt}}
   182  virtual_size: {{.Size}}
   183  `
   184  			} else {
   185  				ctx.Format = `repository: {{ .Repository }}
   186  tag: {{.Tag}}
   187  image_id: {{.ID}}
   188  created_at: {{.CreatedAt}}
   189  virtual_size: {{.Size}}
   190  `
   191  			}
   192  		}
   193  	}
   194  
   195  	ctx.buffer = bytes.NewBufferString("")
   196  	ctx.preformat()
   197  	if ctx.table && ctx.Digest && !strings.Contains(ctx.Format, "{{.Digest}}") {
   198  		ctx.finalFormat += "\t{{.Digest}}"
   199  	}
   200  
   201  	tmpl, err := ctx.parseFormat()
   202  	if err != nil {
   203  		return
   204  	}
   205  
   206  	for _, image := range ctx.Images {
   207  		images := []*imageContext{}
   208  		if isDangling(image) {
   209  			images = append(images, &imageContext{
   210  				trunc:  ctx.Trunc,
   211  				i:      image,
   212  				repo:   "<none>",
   213  				tag:    "<none>",
   214  				digest: "<none>",
   215  			})
   216  		} else {
   217  			repoTags := map[string][]string{}
   218  			repoDigests := map[string][]string{}
   219  
   220  			for _, refString := range append(image.RepoTags) {
   221  				ref, err := reference.ParseNamed(refString)
   222  				if err != nil {
   223  					continue
   224  				}
   225  				if nt, ok := ref.(reference.NamedTagged); ok {
   226  					repoTags[ref.Name()] = append(repoTags[ref.Name()], nt.Tag())
   227  				}
   228  			}
   229  			for _, refString := range append(image.RepoDigests) {
   230  				ref, err := reference.ParseNamed(refString)
   231  				if err != nil {
   232  					continue
   233  				}
   234  				if c, ok := ref.(reference.Canonical); ok {
   235  					repoDigests[ref.Name()] = append(repoDigests[ref.Name()], c.Digest().String())
   236  				}
   237  			}
   238  
   239  			for repo, tags := range repoTags {
   240  				digests := repoDigests[repo]
   241  
   242  				// Do not display digests as their own row
   243  				delete(repoDigests, repo)
   244  
   245  				if !ctx.Digest {
   246  					// Ignore digest references, just show tag once
   247  					digests = nil
   248  				}
   249  
   250  				for _, tag := range tags {
   251  					if len(digests) == 0 {
   252  						images = append(images, &imageContext{
   253  							trunc:  ctx.Trunc,
   254  							i:      image,
   255  							repo:   repo,
   256  							tag:    tag,
   257  							digest: "<none>",
   258  						})
   259  						continue
   260  					}
   261  					// Display the digests for each tag
   262  					for _, dgst := range digests {
   263  						images = append(images, &imageContext{
   264  							trunc:  ctx.Trunc,
   265  							i:      image,
   266  							repo:   repo,
   267  							tag:    tag,
   268  							digest: dgst,
   269  						})
   270  					}
   271  
   272  				}
   273  			}
   274  
   275  			// Show rows for remaining digest only references
   276  			for repo, digests := range repoDigests {
   277  				// If digests are displayed, show row per digest
   278  				if ctx.Digest {
   279  					for _, dgst := range digests {
   280  						images = append(images, &imageContext{
   281  							trunc:  ctx.Trunc,
   282  							i:      image,
   283  							repo:   repo,
   284  							tag:    "<none>",
   285  							digest: dgst,
   286  						})
   287  					}
   288  				} else {
   289  					images = append(images, &imageContext{
   290  						trunc: ctx.Trunc,
   291  						i:     image,
   292  						repo:  repo,
   293  						tag:   "<none>",
   294  					})
   295  				}
   296  			}
   297  		}
   298  		for _, imageCtx := range images {
   299  			err = ctx.contextFormat(tmpl, imageCtx)
   300  			if err != nil {
   301  				return
   302  			}
   303  		}
   304  	}
   305  
   306  	ctx.postformat(tmpl, &imageContext{})
   307  }