image.go 5.1 KB

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