image.go 6.0 KB

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