github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/cli/command/formatter/image.go (about)

     1  package formatter
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/docker/distribution/reference"
     8  	"github.com/docker/docker/api/types"
     9  	"github.com/docker/docker/pkg/stringid"
    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 formatter, 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 image.RepoTags {
    98  				ref, err := reference.ParseNormalizedNamed(refString)
    99  				if err != nil {
   100  					continue
   101  				}
   102  				if nt, ok := ref.(reference.NamedTagged); ok {
   103  					familiarRef := reference.FamiliarName(ref)
   104  					repoTags[familiarRef] = append(repoTags[familiarRef], nt.Tag())
   105  				}
   106  			}
   107  			for _, refString := range image.RepoDigests {
   108  				ref, err := reference.ParseNormalizedNamed(refString)
   109  				if err != nil {
   110  					continue
   111  				}
   112  				if c, ok := ref.(reference.Canonical); ok {
   113  					familiarRef := reference.FamiliarName(ref)
   114  					repoDigests[familiarRef] = append(repoDigests[familiarRef], c.Digest().String())
   115  				}
   116  			}
   117  
   118  			for repo, tags := range repoTags {
   119  				digests := repoDigests[repo]
   120  
   121  				// Do not display digests as their own row
   122  				delete(repoDigests, repo)
   123  
   124  				if !ctx.Digest {
   125  					// Ignore digest references, just show tag once
   126  					digests = nil
   127  				}
   128  
   129  				for _, tag := range tags {
   130  					if len(digests) == 0 {
   131  						images = append(images, &imageContext{
   132  							trunc:  ctx.Trunc,
   133  							i:      image,
   134  							repo:   repo,
   135  							tag:    tag,
   136  							digest: "<none>",
   137  						})
   138  						continue
   139  					}
   140  					// Display the digests for each tag
   141  					for _, dgst := range digests {
   142  						images = append(images, &imageContext{
   143  							trunc:  ctx.Trunc,
   144  							i:      image,
   145  							repo:   repo,
   146  							tag:    tag,
   147  							digest: dgst,
   148  						})
   149  					}
   150  
   151  				}
   152  			}
   153  
   154  			// Show rows for remaining digest only references
   155  			for repo, digests := range repoDigests {
   156  				// If digests are displayed, show row per digest
   157  				if ctx.Digest {
   158  					for _, dgst := range digests {
   159  						images = append(images, &imageContext{
   160  							trunc:  ctx.Trunc,
   161  							i:      image,
   162  							repo:   repo,
   163  							tag:    "<none>",
   164  							digest: dgst,
   165  						})
   166  					}
   167  				} else {
   168  					images = append(images, &imageContext{
   169  						trunc: ctx.Trunc,
   170  						i:     image,
   171  						repo:  repo,
   172  						tag:   "<none>",
   173  					})
   174  				}
   175  			}
   176  		}
   177  		for _, imageCtx := range images {
   178  			if err := format(imageCtx); err != nil {
   179  				return err
   180  			}
   181  		}
   182  	}
   183  	return nil
   184  }
   185  
   186  type imageContext struct {
   187  	HeaderContext
   188  	trunc  bool
   189  	i      types.ImageSummary
   190  	repo   string
   191  	tag    string
   192  	digest string
   193  }
   194  
   195  func (c *imageContext) MarshalJSON() ([]byte, error) {
   196  	return marshalJSON(c)
   197  }
   198  
   199  func (c *imageContext) ID() string {
   200  	c.AddHeader(imageIDHeader)
   201  	if c.trunc {
   202  		return stringid.TruncateID(c.i.ID)
   203  	}
   204  	return c.i.ID
   205  }
   206  
   207  func (c *imageContext) Repository() string {
   208  	c.AddHeader(repositoryHeader)
   209  	return c.repo
   210  }
   211  
   212  func (c *imageContext) Tag() string {
   213  	c.AddHeader(tagHeader)
   214  	return c.tag
   215  }
   216  
   217  func (c *imageContext) Digest() string {
   218  	c.AddHeader(digestHeader)
   219  	return c.digest
   220  }
   221  
   222  func (c *imageContext) CreatedSince() string {
   223  	c.AddHeader(createdSinceHeader)
   224  	createdAt := time.Unix(int64(c.i.Created), 0)
   225  	return units.HumanDuration(time.Now().UTC().Sub(createdAt))
   226  }
   227  
   228  func (c *imageContext) CreatedAt() string {
   229  	c.AddHeader(createdAtHeader)
   230  	return time.Unix(int64(c.i.Created), 0).String()
   231  }
   232  
   233  func (c *imageContext) Size() string {
   234  	c.AddHeader(sizeHeader)
   235  	return units.HumanSizeWithPrecision(float64(c.i.Size), 3)
   236  }
   237  
   238  func (c *imageContext) Containers() string {
   239  	c.AddHeader(containersHeader)
   240  	if c.i.Containers == -1 {
   241  		return "N/A"
   242  	}
   243  	return fmt.Sprintf("%d", c.i.Containers)
   244  }
   245  
   246  func (c *imageContext) VirtualSize() string {
   247  	c.AddHeader(sizeHeader)
   248  	return units.HumanSize(float64(c.i.VirtualSize))
   249  }
   250  
   251  func (c *imageContext) SharedSize() string {
   252  	c.AddHeader(sharedSizeHeader)
   253  	if c.i.SharedSize == -1 {
   254  		return "N/A"
   255  	}
   256  	return units.HumanSize(float64(c.i.SharedSize))
   257  }
   258  
   259  func (c *imageContext) UniqueSize() string {
   260  	c.AddHeader(uniqueSizeHeader)
   261  	if c.i.VirtualSize == -1 || c.i.SharedSize == -1 {
   262  		return "N/A"
   263  	}
   264  	return units.HumanSize(float64(c.i.VirtualSize - c.i.SharedSize))
   265  }