github.com/sld880311/docker@v0.0.0-20200524143708-d5593973a475/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 }