github.com/cspotcode/docker-cli@v20.10.0-rc1.0.20201201121459-3faad7acc5b8+incompatible/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{{if .CreatedSince }}{{.CreatedSince}}{{else}}N/A{{end}}\t{{.Size}}"
    15  	defaultImageTableFormatWithDigest = "table {{.Repository}}\t{{.Tag}}\t{{.Digest}}\t{{.ID}}\t{{if .CreatedSince }}{{.CreatedSince}}{{else}}N/A{{end}}\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(newImageContext(), render)
    80  }
    81  
    82  // needDigest determines whether the image digest should be ignored or not when writing image context
    83  func needDigest(ctx ImageContext) bool {
    84  	return ctx.Digest || ctx.Format.Contains("{{.Digest}}")
    85  }
    86  
    87  func imageFormat(ctx ImageContext, images []types.ImageSummary, format func(subContext SubContext) error) error {
    88  	for _, image := range images {
    89  		formatted := []*imageContext{}
    90  		if isDangling(image) {
    91  			formatted = append(formatted, &imageContext{
    92  				trunc:  ctx.Trunc,
    93  				i:      image,
    94  				repo:   "<none>",
    95  				tag:    "<none>",
    96  				digest: "<none>",
    97  			})
    98  		} else {
    99  			formatted = imageFormatTaggedAndDigest(ctx, image)
   100  		}
   101  		for _, imageCtx := range formatted {
   102  			if err := format(imageCtx); err != nil {
   103  				return err
   104  			}
   105  		}
   106  	}
   107  	return nil
   108  }
   109  
   110  func imageFormatTaggedAndDigest(ctx ImageContext, image types.ImageSummary) []*imageContext {
   111  	repoTags := map[string][]string{}
   112  	repoDigests := map[string][]string{}
   113  	images := []*imageContext{}
   114  
   115  	for _, refString := range image.RepoTags {
   116  		ref, err := reference.ParseNormalizedNamed(refString)
   117  		if err != nil {
   118  			continue
   119  		}
   120  		if nt, ok := ref.(reference.NamedTagged); ok {
   121  			familiarRef := reference.FamiliarName(ref)
   122  			repoTags[familiarRef] = append(repoTags[familiarRef], nt.Tag())
   123  		}
   124  	}
   125  	for _, refString := range image.RepoDigests {
   126  		ref, err := reference.ParseNormalizedNamed(refString)
   127  		if err != nil {
   128  			continue
   129  		}
   130  		if c, ok := ref.(reference.Canonical); ok {
   131  			familiarRef := reference.FamiliarName(ref)
   132  			repoDigests[familiarRef] = append(repoDigests[familiarRef], c.Digest().String())
   133  		}
   134  	}
   135  
   136  	addImage := func(repo, tag, digest string) {
   137  		image := &imageContext{
   138  			trunc:  ctx.Trunc,
   139  			i:      image,
   140  			repo:   repo,
   141  			tag:    tag,
   142  			digest: digest,
   143  		}
   144  		images = append(images, image)
   145  	}
   146  
   147  	for repo, tags := range repoTags {
   148  		digests := repoDigests[repo]
   149  
   150  		// Do not display digests as their own row
   151  		delete(repoDigests, repo)
   152  
   153  		if !needDigest(ctx) {
   154  			// Ignore digest references, just show tag once
   155  			digests = nil
   156  		}
   157  
   158  		for _, tag := range tags {
   159  			if len(digests) == 0 {
   160  				addImage(repo, tag, "<none>")
   161  				continue
   162  			}
   163  			// Display the digests for each tag
   164  			for _, dgst := range digests {
   165  				addImage(repo, tag, dgst)
   166  			}
   167  
   168  		}
   169  	}
   170  
   171  	// Show rows for remaining digest only references
   172  	for repo, digests := range repoDigests {
   173  		// If digests are displayed, show row per digest
   174  		if ctx.Digest {
   175  			for _, dgst := range digests {
   176  				addImage(repo, "<none>", dgst)
   177  			}
   178  		} else {
   179  			addImage(repo, "<none>", "")
   180  
   181  		}
   182  	}
   183  	return images
   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 newImageContext() *imageContext {
   196  	imageCtx := imageContext{}
   197  	imageCtx.Header = SubHeaderContext{
   198  		"ID":           imageIDHeader,
   199  		"Repository":   repositoryHeader,
   200  		"Tag":          tagHeader,
   201  		"Digest":       digestHeader,
   202  		"CreatedSince": CreatedSinceHeader,
   203  		"CreatedAt":    CreatedAtHeader,
   204  		"Size":         SizeHeader,
   205  		"Containers":   containersHeader,
   206  		"VirtualSize":  SizeHeader,
   207  		"SharedSize":   sharedSizeHeader,
   208  		"UniqueSize":   uniqueSizeHeader,
   209  	}
   210  	return &imageCtx
   211  }
   212  
   213  func (c *imageContext) MarshalJSON() ([]byte, error) {
   214  	return MarshalJSON(c)
   215  }
   216  
   217  func (c *imageContext) ID() string {
   218  	if c.trunc {
   219  		return stringid.TruncateID(c.i.ID)
   220  	}
   221  	return c.i.ID
   222  }
   223  
   224  func (c *imageContext) Repository() string {
   225  	return c.repo
   226  }
   227  
   228  func (c *imageContext) Tag() string {
   229  	return c.tag
   230  }
   231  
   232  func (c *imageContext) Digest() string {
   233  	return c.digest
   234  }
   235  
   236  func (c *imageContext) CreatedSince() string {
   237  	createdAt := time.Unix(c.i.Created, 0)
   238  
   239  	if createdAt.IsZero() {
   240  		return ""
   241  	}
   242  
   243  	return units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
   244  }
   245  
   246  func (c *imageContext) CreatedAt() string {
   247  	return time.Unix(c.i.Created, 0).String()
   248  }
   249  
   250  func (c *imageContext) Size() string {
   251  	return units.HumanSizeWithPrecision(float64(c.i.Size), 3)
   252  }
   253  
   254  func (c *imageContext) Containers() string {
   255  	if c.i.Containers == -1 {
   256  		return "N/A"
   257  	}
   258  	return fmt.Sprintf("%d", c.i.Containers)
   259  }
   260  
   261  func (c *imageContext) VirtualSize() string {
   262  	return units.HumanSize(float64(c.i.VirtualSize))
   263  }
   264  
   265  func (c *imageContext) SharedSize() string {
   266  	if c.i.SharedSize == -1 {
   267  		return "N/A"
   268  	}
   269  	return units.HumanSize(float64(c.i.SharedSize))
   270  }
   271  
   272  func (c *imageContext) UniqueSize() string {
   273  	if c.i.VirtualSize == -1 || c.i.SharedSize == -1 {
   274  		return "N/A"
   275  	}
   276  	return units.HumanSize(float64(c.i.VirtualSize - c.i.SharedSize))
   277  }