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