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 }