github.com/sijibomii/docker@v0.0.0-20231230191044-5cf6ca554647/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  		ctx.Format = defaultContainerTableFormat
   118  		if ctx.Quiet {
   119  			ctx.Format = defaultQuietFormat
   120  		}
   121  	case rawFormatKey:
   122  		if ctx.Quiet {
   123  			ctx.Format = `container_id: {{.ID}}`
   124  		} else {
   125  			ctx.Format = `container_id: {{.ID}}
   126  image: {{.Image}}
   127  command: {{.Command}}
   128  created_at: {{.CreatedAt}}
   129  status: {{.Status}}
   130  names: {{.Names}}
   131  labels: {{.Labels}}
   132  ports: {{.Ports}}
   133  `
   134  			if ctx.Size {
   135  				ctx.Format += `size: {{.Size}}
   136  `
   137  			}
   138  		}
   139  	}
   140  
   141  	ctx.buffer = bytes.NewBufferString("")
   142  	ctx.preformat()
   143  	if ctx.table && ctx.Size {
   144  		ctx.finalFormat += "\t{{.Size}}"
   145  	}
   146  
   147  	tmpl, err := ctx.parseFormat()
   148  	if err != nil {
   149  		return
   150  	}
   151  
   152  	for _, container := range ctx.Containers {
   153  		containerCtx := &containerContext{
   154  			trunc: ctx.Trunc,
   155  			c:     container,
   156  		}
   157  		err = ctx.contextFormat(tmpl, containerCtx)
   158  		if err != nil {
   159  			return
   160  		}
   161  	}
   162  
   163  	ctx.postformat(tmpl, &containerContext{})
   164  }
   165  
   166  func (ctx ImageContext) Write() {
   167  	switch ctx.Format {
   168  	case tableFormatKey:
   169  		ctx.Format = defaultImageTableFormat
   170  		if ctx.Digest {
   171  			ctx.Format = defaultImageTableFormatWithDigest
   172  		}
   173  		if ctx.Quiet {
   174  			ctx.Format = defaultQuietFormat
   175  		}
   176  	case rawFormatKey:
   177  		if ctx.Quiet {
   178  			ctx.Format = `image_id: {{.ID}}`
   179  		} else {
   180  			if ctx.Digest {
   181  				ctx.Format = `repository: {{ .Repository }}
   182  tag: {{.Tag}}
   183  digest: {{.Digest}}
   184  image_id: {{.ID}}
   185  created_at: {{.CreatedAt}}
   186  virtual_size: {{.Size}}
   187  `
   188  			} else {
   189  				ctx.Format = `repository: {{ .Repository }}
   190  tag: {{.Tag}}
   191  image_id: {{.ID}}
   192  created_at: {{.CreatedAt}}
   193  virtual_size: {{.Size}}
   194  `
   195  			}
   196  		}
   197  	}
   198  
   199  	ctx.buffer = bytes.NewBufferString("")
   200  	ctx.preformat()
   201  	if ctx.table && ctx.Digest && !strings.Contains(ctx.Format, "{{.Digest}}") {
   202  		ctx.finalFormat += "\t{{.Digest}}"
   203  	}
   204  
   205  	tmpl, err := ctx.parseFormat()
   206  	if err != nil {
   207  		return
   208  	}
   209  
   210  	for _, image := range ctx.Images {
   211  
   212  		repoTags := image.RepoTags
   213  		repoDigests := image.RepoDigests
   214  
   215  		if len(repoTags) == 1 && repoTags[0] == "<none>:<none>" && len(repoDigests) == 1 && repoDigests[0] == "<none>@<none>" {
   216  			// dangling image - clear out either repoTags or repoDigests so we only show it once below
   217  			repoDigests = []string{}
   218  		}
   219  		// combine the tags and digests lists
   220  		tagsAndDigests := append(repoTags, repoDigests...)
   221  		for _, repoAndRef := range tagsAndDigests {
   222  			repo := "<none>"
   223  			tag := "<none>"
   224  			digest := "<none>"
   225  
   226  			if !strings.HasPrefix(repoAndRef, "<none>") {
   227  				ref, err := reference.ParseNamed(repoAndRef)
   228  				if err != nil {
   229  					continue
   230  				}
   231  				repo = ref.Name()
   232  
   233  				switch x := ref.(type) {
   234  				case reference.Canonical:
   235  					digest = x.Digest().String()
   236  				case reference.NamedTagged:
   237  					tag = x.Tag()
   238  				}
   239  			}
   240  			imageCtx := &imageContext{
   241  				trunc:  ctx.Trunc,
   242  				i:      image,
   243  				repo:   repo,
   244  				tag:    tag,
   245  				digest: digest,
   246  			}
   247  			err = ctx.contextFormat(tmpl, imageCtx)
   248  			if err != nil {
   249  				return
   250  			}
   251  		}
   252  	}
   253  
   254  	ctx.postformat(tmpl, &imageContext{})
   255  }