github.com/olljanat/moby@v1.13.1/cli/command/formatter/image.go (about)

     1  package formatter
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/docker/docker/api/types"
     8  	"github.com/docker/docker/pkg/stringid"
     9  	"github.com/docker/docker/reference"
    10  	units "github.com/docker/go-units"
    11  )
    12  
    13  const (
    14  	defaultImageTableFormat           = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.Size}}"
    15  	defaultImageTableFormatWithDigest = "table {{.Repository}}\t{{.Tag}}\t{{.Digest}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.Size}}"
    16  
    17  	imageIDHeader    = "IMAGE ID"
    18  	repositoryHeader = "REPOSITORY"
    19  	tagHeader        = "TAG"
    20  	digestHeader     = "DIGEST"
    21  )
    22  
    23  // ImageContext contains image specific information required by the formater, encapsulate a Context struct.
    24  type ImageContext struct {
    25  	Context
    26  	Digest bool
    27  }
    28  
    29  func isDangling(image types.ImageSummary) bool {
    30  	return len(image.RepoTags) == 1 && image.RepoTags[0] == "<none>:<none>" && len(image.RepoDigests) == 1 && image.RepoDigests[0] == "<none>@<none>"
    31  }
    32  
    33  // NewImageFormat returns a format for rendering an ImageContext
    34  func NewImageFormat(source string, quiet bool, digest bool) Format {
    35  	switch source {
    36  	case TableFormatKey:
    37  		switch {
    38  		case quiet:
    39  			return defaultQuietFormat
    40  		case digest:
    41  			return defaultImageTableFormatWithDigest
    42  		default:
    43  			return defaultImageTableFormat
    44  		}
    45  	case RawFormatKey:
    46  		switch {
    47  		case quiet:
    48  			return `image_id: {{.ID}}`
    49  		case digest:
    50  			return `repository: {{ .Repository }}
    51  tag: {{.Tag}}
    52  digest: {{.Digest}}
    53  image_id: {{.ID}}
    54  created_at: {{.CreatedAt}}
    55  virtual_size: {{.Size}}
    56  `
    57  		default:
    58  			return `repository: {{ .Repository }}
    59  tag: {{.Tag}}
    60  image_id: {{.ID}}
    61  created_at: {{.CreatedAt}}
    62  virtual_size: {{.Size}}
    63  `
    64  		}
    65  	}
    66  
    67  	format := Format(source)
    68  	if format.IsTable() && digest && !format.Contains("{{.Digest}}") {
    69  		format += "\t{{.Digest}}"
    70  	}
    71  	return format
    72  }
    73  
    74  // ImageWrite writes the formatter images using the ImageContext
    75  func ImageWrite(ctx ImageContext, images []types.ImageSummary) error {
    76  	render := func(format func(subContext subContext) error) error {
    77  		return imageFormat(ctx, images, format)
    78  	}
    79  	return ctx.Write(&imageContext{}, render)
    80  }
    81  
    82  func imageFormat(ctx ImageContext, images []types.ImageSummary, format func(subContext subContext) error) error {
    83  	for _, image := range images {
    84  		images := []*imageContext{}
    85  		if isDangling(image) {
    86  			images = append(images, &imageContext{
    87  				trunc:  ctx.Trunc,
    88  				i:      image,
    89  				repo:   "<none>",
    90  				tag:    "<none>",
    91  				digest: "<none>",
    92  			})
    93  		} else {
    94  			repoTags := map[string][]string{}
    95  			repoDigests := map[string][]string{}
    96  
    97  			for _, refString := range append(image.RepoTags) {
    98  				ref, err := reference.ParseNamed(refString)
    99  				if err != nil {
   100  					continue
   101  				}
   102  				if nt, ok := ref.(reference.NamedTagged); ok {
   103  					repoTags[ref.Name()] = append(repoTags[ref.Name()], nt.Tag())
   104  				}
   105  			}
   106  			for _, refString := range append(image.RepoDigests) {
   107  				ref, err := reference.ParseNamed(refString)
   108  				if err != nil {
   109  					continue
   110  				}
   111  				if c, ok := ref.(reference.Canonical); ok {
   112  					repoDigests[ref.Name()] = append(repoDigests[ref.Name()], c.Digest().String())
   113  				}
   114  			}
   115  
   116  			for repo, tags := range repoTags {
   117  				digests := repoDigests[repo]
   118  
   119  				// Do not display digests as their own row
   120  				delete(repoDigests, repo)
   121  
   122  				if !ctx.Digest {
   123  					// Ignore digest references, just show tag once
   124  					digests = nil
   125  				}
   126  
   127  				for _, tag := range tags {
   128  					if len(digests) == 0 {
   129  						images = append(images, &imageContext{
   130  							trunc:  ctx.Trunc,
   131  							i:      image,
   132  							repo:   repo,
   133  							tag:    tag,
   134  							digest: "<none>",
   135  						})
   136  						continue
   137  					}
   138  					// Display the digests for each tag
   139  					for _, dgst := range digests {
   140  						images = append(images, &imageContext{
   141  							trunc:  ctx.Trunc,
   142  							i:      image,
   143  							repo:   repo,
   144  							tag:    tag,
   145  							digest: dgst,
   146  						})
   147  					}
   148  
   149  				}
   150  			}
   151  
   152  			// Show rows for remaining digest only references
   153  			for repo, digests := range repoDigests {
   154  				// If digests are displayed, show row per digest
   155  				if ctx.Digest {
   156  					for _, dgst := range digests {
   157  						images = append(images, &imageContext{
   158  							trunc:  ctx.Trunc,
   159  							i:      image,
   160  							repo:   repo,
   161  							tag:    "<none>",
   162  							digest: dgst,
   163  						})
   164  					}
   165  				} else {
   166  					images = append(images, &imageContext{
   167  						trunc: ctx.Trunc,
   168  						i:     image,
   169  						repo:  repo,
   170  						tag:   "<none>",
   171  					})
   172  				}
   173  			}
   174  		}
   175  		for _, imageCtx := range images {
   176  			if err := format(imageCtx); err != nil {
   177  				return err
   178  			}
   179  		}
   180  	}
   181  	return nil
   182  }
   183  
   184  type imageContext struct {
   185  	HeaderContext
   186  	trunc  bool
   187  	i      types.ImageSummary
   188  	repo   string
   189  	tag    string
   190  	digest string
   191  }
   192  
   193  func (c *imageContext) ID() string {
   194  	c.AddHeader(imageIDHeader)
   195  	if c.trunc {
   196  		return stringid.TruncateID(c.i.ID)
   197  	}
   198  	return c.i.ID
   199  }
   200  
   201  func (c *imageContext) Repository() string {
   202  	c.AddHeader(repositoryHeader)
   203  	return c.repo
   204  }
   205  
   206  func (c *imageContext) Tag() string {
   207  	c.AddHeader(tagHeader)
   208  	return c.tag
   209  }
   210  
   211  func (c *imageContext) Digest() string {
   212  	c.AddHeader(digestHeader)
   213  	return c.digest
   214  }
   215  
   216  func (c *imageContext) CreatedSince() string {
   217  	c.AddHeader(createdSinceHeader)
   218  	createdAt := time.Unix(int64(c.i.Created), 0)
   219  	return units.HumanDuration(time.Now().UTC().Sub(createdAt))
   220  }
   221  
   222  func (c *imageContext) CreatedAt() string {
   223  	c.AddHeader(createdAtHeader)
   224  	return time.Unix(int64(c.i.Created), 0).String()
   225  }
   226  
   227  func (c *imageContext) Size() string {
   228  	c.AddHeader(sizeHeader)
   229  	return units.HumanSizeWithPrecision(float64(c.i.Size), 3)
   230  }
   231  
   232  func (c *imageContext) Containers() string {
   233  	c.AddHeader(containersHeader)
   234  	if c.i.Containers == -1 {
   235  		return "N/A"
   236  	}
   237  	return fmt.Sprintf("%d", c.i.Containers)
   238  }
   239  
   240  func (c *imageContext) VirtualSize() string {
   241  	c.AddHeader(sizeHeader)
   242  	return units.HumanSize(float64(c.i.VirtualSize))
   243  }
   244  
   245  func (c *imageContext) SharedSize() string {
   246  	c.AddHeader(sharedSizeHeader)
   247  	if c.i.SharedSize == -1 {
   248  		return "N/A"
   249  	}
   250  	return units.HumanSize(float64(c.i.SharedSize))
   251  }
   252  
   253  func (c *imageContext) UniqueSize() string {
   254  	c.AddHeader(uniqueSizeHeader)
   255  	if c.i.VirtualSize == -1 || c.i.SharedSize == -1 {
   256  		return "N/A"
   257  	}
   258  	return units.HumanSize(float64(c.i.VirtualSize - c.i.SharedSize))
   259  }