container.go 7.6 KB

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