container.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. /*
  2. Copyright 2020 Docker Compose CLI authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package formatter
  14. import (
  15. "fmt"
  16. "strconv"
  17. "strings"
  18. "time"
  19. "github.com/docker/cli/cli/command/formatter"
  20. "github.com/docker/compose/v2/pkg/api"
  21. "github.com/docker/docker/api/types"
  22. "github.com/docker/docker/pkg/stringid"
  23. "github.com/docker/go-units"
  24. )
  25. const (
  26. defaultContainerTableFormat = "table {{.Name}}\t{{.Image}}\t{{.Command}}\t{{.Service}}\t{{.RunningFor}}\t{{.Status}}\t{{.Ports}}"
  27. nameHeader = "NAME"
  28. projectHeader = "PROJECT"
  29. serviceHeader = "SERVICE"
  30. commandHeader = "COMMAND"
  31. runningForHeader = "CREATED"
  32. mountsHeader = "MOUNTS"
  33. localVolumes = "LOCAL VOLUMES"
  34. networksHeader = "NETWORKS"
  35. )
  36. // NewContainerFormat returns a Format for rendering using a Context
  37. func NewContainerFormat(source string, quiet bool, size bool) formatter.Format {
  38. switch source {
  39. case formatter.TableFormatKey, "": // table formatting is the default if none is set.
  40. if quiet {
  41. return formatter.DefaultQuietFormat
  42. }
  43. format := defaultContainerTableFormat
  44. if size {
  45. format += `\t{{.Size}}`
  46. }
  47. return formatter.Format(format)
  48. case formatter.RawFormatKey:
  49. if quiet {
  50. return `container_id: {{.ID}}`
  51. }
  52. format := `container_id: {{.ID}}
  53. image: {{.Image}}
  54. command: {{.Command}}
  55. created_at: {{.CreatedAt}}
  56. state: {{- pad .State 1 0}}
  57. status: {{- pad .Status 1 0}}
  58. names: {{.Names}}
  59. labels: {{- pad .Labels 1 0}}
  60. ports: {{- pad .Ports 1 0}}
  61. `
  62. if size {
  63. format += `size: {{.Size}}\n`
  64. }
  65. return formatter.Format(format)
  66. default: // custom format
  67. if quiet {
  68. return formatter.DefaultQuietFormat
  69. }
  70. return formatter.Format(source)
  71. }
  72. }
  73. // ContainerWrite renders the context for a list of containers
  74. func ContainerWrite(ctx formatter.Context, containers []api.ContainerSummary) error {
  75. render := func(format func(subContext formatter.SubContext) error) error {
  76. for _, container := range containers {
  77. err := format(&ContainerContext{trunc: ctx.Trunc, c: container})
  78. if err != nil {
  79. return err
  80. }
  81. }
  82. return nil
  83. }
  84. return ctx.Write(NewContainerContext(), render)
  85. }
  86. // ContainerContext is a struct used for rendering a list of containers in a Go template.
  87. type ContainerContext struct {
  88. formatter.HeaderContext
  89. trunc bool
  90. c api.ContainerSummary
  91. // FieldsUsed is used in the pre-processing step to detect which fields are
  92. // used in the template. It's currently only used to detect use of the .Size
  93. // field which (if used) automatically sets the '--size' option when making
  94. // the API call.
  95. FieldsUsed map[string]interface{}
  96. }
  97. // NewContainerContext creates a new context for rendering containers
  98. func NewContainerContext() *ContainerContext {
  99. containerCtx := ContainerContext{}
  100. containerCtx.Header = formatter.SubHeaderContext{
  101. "ID": formatter.ContainerIDHeader,
  102. "Name": nameHeader,
  103. "Project": projectHeader,
  104. "Service": serviceHeader,
  105. "Image": formatter.ImageHeader,
  106. "Command": commandHeader,
  107. "CreatedAt": formatter.CreatedAtHeader,
  108. "RunningFor": runningForHeader,
  109. "Ports": formatter.PortsHeader,
  110. "State": formatter.StateHeader,
  111. "Status": formatter.StatusHeader,
  112. "Size": formatter.SizeHeader,
  113. "Labels": formatter.LabelsHeader,
  114. }
  115. return &containerCtx
  116. }
  117. // MarshalJSON makes ContainerContext implement json.Marshaler
  118. func (c *ContainerContext) MarshalJSON() ([]byte, error) {
  119. return formatter.MarshalJSON(c)
  120. }
  121. // ID returns the container's ID as a string. Depending on the `--no-trunc`
  122. // option being set, the full or truncated ID is returned.
  123. func (c *ContainerContext) ID() string {
  124. if c.trunc {
  125. return stringid.TruncateID(c.c.ID)
  126. }
  127. return c.c.ID
  128. }
  129. func (c *ContainerContext) Name() string {
  130. return c.c.Name
  131. }
  132. // Names returns a comma-separated string of the container's names, with their
  133. // slash (/) prefix stripped. Additional names for the container (related to the
  134. // legacy `--link` feature) are omitted.
  135. func (c *ContainerContext) Names() string {
  136. names := formatter.StripNamePrefix(c.c.Names)
  137. if c.trunc {
  138. for _, name := range names {
  139. if len(strings.Split(name, "/")) == 1 {
  140. names = []string{name}
  141. break
  142. }
  143. }
  144. }
  145. return strings.Join(names, ",")
  146. }
  147. func (c *ContainerContext) Service() string {
  148. return c.c.Service
  149. }
  150. func (c *ContainerContext) Project() string {
  151. return c.c.Project
  152. }
  153. func (c *ContainerContext) Image() string {
  154. return c.c.Image
  155. }
  156. func (c *ContainerContext) Command() string {
  157. command := c.c.Command
  158. if c.trunc {
  159. command = formatter.Ellipsis(command, 20)
  160. }
  161. return strconv.Quote(command)
  162. }
  163. func (c *ContainerContext) CreatedAt() string {
  164. return time.Unix(c.c.Created, 0).String()
  165. }
  166. func (c *ContainerContext) RunningFor() string {
  167. createdAt := time.Unix(c.c.Created, 0)
  168. return units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
  169. }
  170. func (c *ContainerContext) ExitCode() int {
  171. return c.c.ExitCode
  172. }
  173. func (c *ContainerContext) State() string {
  174. return c.c.State
  175. }
  176. func (c *ContainerContext) Status() string {
  177. return c.c.Status
  178. }
  179. func (c *ContainerContext) Health() string {
  180. return c.c.Health
  181. }
  182. func (c *ContainerContext) Publishers() api.PortPublishers {
  183. return c.c.Publishers
  184. }
  185. func (c *ContainerContext) Ports() string {
  186. var ports []types.Port
  187. for _, publisher := range c.c.Publishers {
  188. ports = append(ports, types.Port{
  189. IP: publisher.URL,
  190. PrivatePort: uint16(publisher.TargetPort),
  191. PublicPort: uint16(publisher.PublishedPort),
  192. Type: publisher.Protocol,
  193. })
  194. }
  195. return formatter.DisplayablePorts(ports)
  196. }
  197. // Labels returns a comma-separated string of labels present on the container.
  198. func (c *ContainerContext) Labels() string {
  199. if c.c.Labels == nil {
  200. return ""
  201. }
  202. var joinLabels []string
  203. for k, v := range c.c.Labels {
  204. joinLabels = append(joinLabels, fmt.Sprintf("%s=%s", k, v))
  205. }
  206. return strings.Join(joinLabels, ",")
  207. }
  208. // Label returns the value of the label with the given name or an empty string
  209. // if the given label does not exist.
  210. func (c *ContainerContext) Label(name string) string {
  211. if c.c.Labels == nil {
  212. return ""
  213. }
  214. return c.c.Labels[name]
  215. }
  216. // Mounts returns a comma-separated string of mount names present on the container.
  217. // If the trunc option is set, names can be truncated (ellipsized).
  218. func (c *ContainerContext) Mounts() string {
  219. var mounts []string
  220. for _, name := range c.c.Mounts {
  221. if c.trunc {
  222. name = formatter.Ellipsis(name, 15)
  223. }
  224. mounts = append(mounts, name)
  225. }
  226. return strings.Join(mounts, ",")
  227. }
  228. // LocalVolumes returns the number of volumes using the "local" volume driver.
  229. func (c *ContainerContext) LocalVolumes() string {
  230. return fmt.Sprintf("%d", c.c.LocalVolumes)
  231. }
  232. // Networks returns a comma-separated string of networks that the container is
  233. // attached to.
  234. func (c *ContainerContext) Networks() string {
  235. return strings.Join(c.c.Networks, ",")
  236. }
  237. // Size returns the container's size and virtual size (e.g. "2B (virtual 21.5MB)")
  238. func (c *ContainerContext) Size() string {
  239. if c.FieldsUsed == nil {
  240. c.FieldsUsed = map[string]interface{}{}
  241. }
  242. c.FieldsUsed["Size"] = struct{}{}
  243. srw := units.HumanSizeWithPrecision(float64(c.c.SizeRw), 3)
  244. sv := units.HumanSizeWithPrecision(float64(c.c.SizeRootFs), 3)
  245. sf := srw
  246. if c.c.SizeRootFs > 0 {
  247. sf = fmt.Sprintf("%s (virtual %s)", srw, sv)
  248. }
  249. return sf
  250. }