Docker-DCO-1.1-Signed-off-by: David Calavera <david.calavera@gmail.com>
| ... | ... |
@@ -179,6 +179,10 @@ func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
|
| 179 | 179 |
return nil |
| 180 | 180 |
} |
| 181 | 181 |
|
| 182 |
+func (cli *DockerCli) PsFormat() string {
|
|
| 183 |
+ return cli.configFile.PsFormat |
|
| 184 |
+} |
|
| 185 |
+ |
|
| 182 | 186 |
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. |
| 183 | 187 |
// The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config |
| 184 | 188 |
// is set the client scheme will be set to https. |
| ... | ... |
@@ -2,21 +2,14 @@ package client |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"encoding/json" |
| 5 |
- "fmt" |
|
| 6 | 5 |
"net/url" |
| 7 | 6 |
"strconv" |
| 8 |
- "strings" |
|
| 9 |
- "text/tabwriter" |
|
| 10 |
- "time" |
|
| 11 | 7 |
|
| 12 |
- "github.com/docker/docker/api" |
|
| 8 |
+ "github.com/docker/docker/api/client/ps" |
|
| 13 | 9 |
"github.com/docker/docker/api/types" |
| 14 | 10 |
"github.com/docker/docker/opts" |
| 15 | 11 |
flag "github.com/docker/docker/pkg/mflag" |
| 16 | 12 |
"github.com/docker/docker/pkg/parsers/filters" |
| 17 |
- "github.com/docker/docker/pkg/stringid" |
|
| 18 |
- "github.com/docker/docker/pkg/stringutils" |
|
| 19 |
- "github.com/docker/docker/pkg/units" |
|
| 20 | 13 |
) |
| 21 | 14 |
|
| 22 | 15 |
// CmdPs outputs a list of Docker containers. |
| ... | ... |
@@ -38,7 +31,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
| 38 | 38 |
since = cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show created since Id or Name, include non-running")
|
| 39 | 39 |
before = cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name")
|
| 40 | 40 |
last = cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running")
|
| 41 |
- fields = cmd.String([]string{"-fields"}, "cimtspn", "Choose fields to print, and order (c,i,m,t,s,p,n,z)")
|
|
| 41 |
+ format = cmd.String([]string{"F", "-format"}, "", "Pretty-print containers using a Go template")
|
|
| 42 | 42 |
flFilter = opts.NewListOpts(nil) |
| 43 | 43 |
) |
| 44 | 44 |
cmd.Require(flag.Exact, 0) |
| ... | ... |
@@ -99,136 +92,24 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
| 99 | 99 |
return err |
| 100 | 100 |
} |
| 101 | 101 |
|
| 102 |
- w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
| 103 |
- if *quiet {
|
|
| 104 |
- *fields = "c" |
|
| 105 |
- } |
|
| 106 |
- |
|
| 107 |
- if *size {
|
|
| 108 |
- *fields = *fields + "z" |
|
| 109 |
- } |
|
| 110 |
- |
|
| 111 |
- if !*quiet {
|
|
| 112 |
- headermap := map[rune]string{
|
|
| 113 |
- 'c': "CONTAINER ID", |
|
| 114 |
- 'i': "IMAGE", |
|
| 115 |
- 'm': "COMMAND", |
|
| 116 |
- 's': "STATUS", |
|
| 117 |
- 't': "CREATED", |
|
| 118 |
- 'p': "PORTS", |
|
| 119 |
- 'n': "NAMES", |
|
| 120 |
- 'z': "SIZE", |
|
| 121 |
- } |
|
| 122 |
- |
|
| 123 |
- headers := make([]string, 0) |
|
| 124 |
- for _, v := range *fields {
|
|
| 125 |
- if title, ok := headermap[v]; ok {
|
|
| 126 |
- headers = append(headers, title) |
|
| 127 |
- } |
|
| 128 |
- } |
|
| 129 |
- |
|
| 130 |
- if len(headers) > 0 {
|
|
| 131 |
- fmt.Fprint(w, strings.Join(headers, "\t")+"\n") |
|
| 132 |
- } |
|
| 133 |
- } |
|
| 134 |
- |
|
| 135 |
- stripNamePrefix := func(ss []string) []string {
|
|
| 136 |
- for i, s := range ss {
|
|
| 137 |
- ss[i] = s[1:] |
|
| 138 |
- } |
|
| 139 |
- |
|
| 140 |
- return ss |
|
| 141 |
- } |
|
| 142 |
- |
|
| 143 |
- type containerMeta struct {
|
|
| 144 |
- c string |
|
| 145 |
- i string |
|
| 146 |
- m string |
|
| 147 |
- t string |
|
| 148 |
- s string |
|
| 149 |
- p string |
|
| 150 |
- n string |
|
| 151 |
- z string |
|
| 152 |
- } |
|
| 153 |
- |
|
| 154 |
- var displayPort string |
|
| 155 |
- if container.HostConfig.NetworkMode == "host" {
|
|
| 156 |
- displayPort = "*/tcp, */udp" |
|
| 157 |
- } else {
|
|
| 158 |
- displayPort = api.DisplayablePorts(container.Ports) |
|
| 159 |
- } |
|
| 160 |
- |
|
| 161 |
- outp := make([]containerMeta, 0) |
|
| 162 |
- for _, container := range containers {
|
|
| 163 |
- next := containerMeta{
|
|
| 164 |
- c: container.ID, |
|
| 165 |
- n: "", |
|
| 166 |
- m: strconv.Quote(container.Command), |
|
| 167 |
- i: container.Image, |
|
| 168 |
- t: units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(container.Created), 0))) + " ago", |
|
| 169 |
- s: container.Status, |
|
| 170 |
- p: displayPort, |
|
| 171 |
- z: fmt.Sprintf("%s", units.HumanSize(float64(container.SizeRw))),
|
|
| 172 |
- } |
|
| 173 |
- |
|
| 174 |
- // handle truncation |
|
| 175 |
- outNames := stripNamePrefix(container.Names) |
|
| 176 |
- if !*noTrunc {
|
|
| 177 |
- next.c = stringid.TruncateID(next.c) |
|
| 178 |
- next.m = stringutils.Truncate(next.m, 20) |
|
| 179 |
- // only display the default name for the container with notrunc is passed |
|
| 180 |
- for _, name := range outNames {
|
|
| 181 |
- if len(strings.Split(name, "/")) == 1 {
|
|
| 182 |
- outNames = []string{name}
|
|
| 183 |
- break |
|
| 184 |
- } |
|
| 185 |
- } |
|
| 102 |
+ f := *format |
|
| 103 |
+ if len(f) == 0 {
|
|
| 104 |
+ if len(cli.PsFormat()) > 0 {
|
|
| 105 |
+ f = cli.PsFormat() |
|
| 106 |
+ } else {
|
|
| 107 |
+ f = "table" |
|
| 186 | 108 |
} |
| 187 |
- next.n = strings.Join(outNames, ",") |
|
| 188 |
- |
|
| 189 |
- if next.i == "" {
|
|
| 190 |
- next.i = "<no image>" |
|
| 191 |
- } |
|
| 192 |
- |
|
| 193 |
- // handle rootfs sizing |
|
| 194 |
- if container.SizeRootFs > 0 {
|
|
| 195 |
- next.z = next.z + fmt.Sprintf(" (virtual %s)", units.HumanSize(float64(container.SizeRootFs)))
|
|
| 196 |
- } |
|
| 197 |
- |
|
| 198 |
- outp = append(outp, next) |
|
| 199 | 109 |
} |
| 200 | 110 |
|
| 201 |
- for _, out := range outp {
|
|
| 202 |
- of := make([]string, 0) |
|
| 203 |
- for _, v := range *fields {
|
|
| 204 |
- switch v {
|
|
| 205 |
- case 'c': |
|
| 206 |
- of = append(of, out.c) |
|
| 207 |
- case 'i': |
|
| 208 |
- of = append(of, out.i) |
|
| 209 |
- case 'm': |
|
| 210 |
- of = append(of, out.m) |
|
| 211 |
- case 't': |
|
| 212 |
- of = append(of, out.t) |
|
| 213 |
- case 's': |
|
| 214 |
- of = append(of, out.s) |
|
| 215 |
- case 'p': |
|
| 216 |
- of = append(of, out.p) |
|
| 217 |
- case 'n': |
|
| 218 |
- of = append(of, out.n) |
|
| 219 |
- case 'z': |
|
| 220 |
- of = append(of, out.z) |
|
| 221 |
- |
|
| 222 |
- } |
|
| 223 |
- } |
|
| 224 |
- if len(of) > 0 {
|
|
| 225 |
- fmt.Fprintf(w, "%s\n", strings.Join(of, "\t")) |
|
| 226 |
- } |
|
| 111 |
+ psCtx := ps.Context{
|
|
| 112 |
+ Output: cli.out, |
|
| 113 |
+ Format: f, |
|
| 114 |
+ Quiet: *quiet, |
|
| 115 |
+ Size: *size, |
|
| 116 |
+ Trunc: !*noTrunc, |
|
| 227 | 117 |
} |
| 228 | 118 |
|
| 229 |
- if !*quiet {
|
|
| 230 |
- w.Flush() |
|
| 231 |
- } |
|
| 119 |
+ ps.Format(psCtx, containers) |
|
| 232 | 120 |
|
| 233 | 121 |
return nil |
| 234 | 122 |
} |
| 235 | 123 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,210 @@ |
| 0 |
+package ps |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "strconv" |
|
| 6 |
+ "strings" |
|
| 7 |
+ "text/tabwriter" |
|
| 8 |
+ "text/template" |
|
| 9 |
+ "time" |
|
| 10 |
+ |
|
| 11 |
+ "github.com/docker/docker/api" |
|
| 12 |
+ "github.com/docker/docker/api/types" |
|
| 13 |
+ "github.com/docker/docker/pkg/stringid" |
|
| 14 |
+ "github.com/docker/docker/pkg/stringutils" |
|
| 15 |
+ "github.com/docker/docker/pkg/units" |
|
| 16 |
+) |
|
| 17 |
+ |
|
| 18 |
+const ( |
|
| 19 |
+ tableKey = "table" |
|
| 20 |
+ |
|
| 21 |
+ idHeader = "CONTAINER ID" |
|
| 22 |
+ imageHeader = "IMAGE" |
|
| 23 |
+ namesHeader = "NAMES" |
|
| 24 |
+ commandHeader = "COMMAND" |
|
| 25 |
+ createdAtHeader = "CREATED AT" |
|
| 26 |
+ runningForHeader = "CREATED" |
|
| 27 |
+ statusHeader = "STATUS" |
|
| 28 |
+ portsHeader = "PORTS" |
|
| 29 |
+ sizeHeader = "SIZE" |
|
| 30 |
+ labelsHeader = "LABELS" |
|
| 31 |
+) |
|
| 32 |
+ |
|
| 33 |
+type containerContext struct {
|
|
| 34 |
+ trunc bool |
|
| 35 |
+ header []string |
|
| 36 |
+ c types.Container |
|
| 37 |
+} |
|
| 38 |
+ |
|
| 39 |
+func (c *containerContext) ID() string {
|
|
| 40 |
+ c.addHeader(idHeader) |
|
| 41 |
+ if c.trunc {
|
|
| 42 |
+ return stringid.TruncateID(c.c.ID) |
|
| 43 |
+ } |
|
| 44 |
+ return c.c.ID |
|
| 45 |
+} |
|
| 46 |
+ |
|
| 47 |
+func (c *containerContext) Names() string {
|
|
| 48 |
+ c.addHeader(namesHeader) |
|
| 49 |
+ names := stripNamePrefix(c.c.Names) |
|
| 50 |
+ if c.trunc {
|
|
| 51 |
+ for _, name := range names {
|
|
| 52 |
+ if len(strings.Split(name, "/")) == 1 {
|
|
| 53 |
+ names = []string{name}
|
|
| 54 |
+ break |
|
| 55 |
+ } |
|
| 56 |
+ } |
|
| 57 |
+ } |
|
| 58 |
+ return strings.Join(names, ",") |
|
| 59 |
+} |
|
| 60 |
+ |
|
| 61 |
+func (c *containerContext) Image() string {
|
|
| 62 |
+ c.addHeader(imageHeader) |
|
| 63 |
+ if c.c.Image == "" {
|
|
| 64 |
+ return "<no image>" |
|
| 65 |
+ } |
|
| 66 |
+ return c.c.Image |
|
| 67 |
+} |
|
| 68 |
+ |
|
| 69 |
+func (c *containerContext) Command() string {
|
|
| 70 |
+ c.addHeader(commandHeader) |
|
| 71 |
+ command := c.c.Command |
|
| 72 |
+ if c.trunc {
|
|
| 73 |
+ command = stringutils.Truncate(command, 20) |
|
| 74 |
+ } |
|
| 75 |
+ return strconv.Quote(command) |
|
| 76 |
+} |
|
| 77 |
+ |
|
| 78 |
+func (c *containerContext) CreatedAt() string {
|
|
| 79 |
+ c.addHeader(createdAtHeader) |
|
| 80 |
+ return time.Unix(int64(c.c.Created), 0).String() |
|
| 81 |
+} |
|
| 82 |
+ |
|
| 83 |
+func (c *containerContext) RunningFor() string {
|
|
| 84 |
+ c.addHeader(runningForHeader) |
|
| 85 |
+ createdAt := time.Unix(int64(c.c.Created), 0) |
|
| 86 |
+ return units.HumanDuration(time.Now().UTC().Sub(createdAt)) |
|
| 87 |
+} |
|
| 88 |
+ |
|
| 89 |
+func (c *containerContext) Ports() string {
|
|
| 90 |
+ c.addHeader(portsHeader) |
|
| 91 |
+ return api.DisplayablePorts(c.c.Ports) |
|
| 92 |
+} |
|
| 93 |
+ |
|
| 94 |
+func (c *containerContext) Status() string {
|
|
| 95 |
+ c.addHeader(statusHeader) |
|
| 96 |
+ return c.c.Status |
|
| 97 |
+} |
|
| 98 |
+ |
|
| 99 |
+func (c *containerContext) Size() string {
|
|
| 100 |
+ c.addHeader(sizeHeader) |
|
| 101 |
+ srw := units.HumanSize(float64(c.c.SizeRw)) |
|
| 102 |
+ sv := units.HumanSize(float64(c.c.SizeRootFs)) |
|
| 103 |
+ |
|
| 104 |
+ sf := srw |
|
| 105 |
+ if c.c.SizeRootFs > 0 {
|
|
| 106 |
+ sf = fmt.Sprintf("%s (virtual %s)", srw, sv)
|
|
| 107 |
+ } |
|
| 108 |
+ return sf |
|
| 109 |
+} |
|
| 110 |
+ |
|
| 111 |
+func (c *containerContext) Labels() string {
|
|
| 112 |
+ c.addHeader(labelsHeader) |
|
| 113 |
+ if c.c.Labels == nil {
|
|
| 114 |
+ return "" |
|
| 115 |
+ } |
|
| 116 |
+ |
|
| 117 |
+ var joinLabels []string |
|
| 118 |
+ for k, v := range c.c.Labels {
|
|
| 119 |
+ joinLabels = append(joinLabels, fmt.Sprintf("%s=%s", k, v))
|
|
| 120 |
+ } |
|
| 121 |
+ return strings.Join(joinLabels, ",") |
|
| 122 |
+} |
|
| 123 |
+ |
|
| 124 |
+func (c *containerContext) Label(name string) string {
|
|
| 125 |
+ n := strings.Split(name, ".") |
|
| 126 |
+ r := strings.NewReplacer("-", " ", "_", " ")
|
|
| 127 |
+ h := r.Replace(n[len(n)-1]) |
|
| 128 |
+ |
|
| 129 |
+ c.addHeader(h) |
|
| 130 |
+ |
|
| 131 |
+ if c.c.Labels == nil {
|
|
| 132 |
+ return "" |
|
| 133 |
+ } |
|
| 134 |
+ return c.c.Labels[name] |
|
| 135 |
+} |
|
| 136 |
+ |
|
| 137 |
+func (c *containerContext) fullHeader() string {
|
|
| 138 |
+ if c.header == nil {
|
|
| 139 |
+ return "" |
|
| 140 |
+ } |
|
| 141 |
+ return strings.Join(c.header, "\t") |
|
| 142 |
+} |
|
| 143 |
+ |
|
| 144 |
+func (c *containerContext) addHeader(header string) {
|
|
| 145 |
+ if c.header == nil {
|
|
| 146 |
+ c.header = []string{}
|
|
| 147 |
+ } |
|
| 148 |
+ c.header = append(c.header, strings.ToUpper(header)) |
|
| 149 |
+} |
|
| 150 |
+ |
|
| 151 |
+func customFormat(ctx Context, containers []types.Container) {
|
|
| 152 |
+ var ( |
|
| 153 |
+ table bool |
|
| 154 |
+ header string |
|
| 155 |
+ format = ctx.Format |
|
| 156 |
+ buffer = bytes.NewBufferString("")
|
|
| 157 |
+ ) |
|
| 158 |
+ |
|
| 159 |
+ if strings.HasPrefix(ctx.Format, tableKey) {
|
|
| 160 |
+ table = true |
|
| 161 |
+ format = format[len(tableKey):] |
|
| 162 |
+ } |
|
| 163 |
+ |
|
| 164 |
+ format = strings.Trim(format, " ") |
|
| 165 |
+ r := strings.NewReplacer(`\t`, "\t", `\n`, "\n") |
|
| 166 |
+ format = r.Replace(format) |
|
| 167 |
+ |
|
| 168 |
+ if table && ctx.Size {
|
|
| 169 |
+ format += "\t{{.Size}}"
|
|
| 170 |
+ } |
|
| 171 |
+ |
|
| 172 |
+ tmpl, err := template.New("ps template").Parse(format)
|
|
| 173 |
+ if err != nil {
|
|
| 174 |
+ buffer.WriteString(fmt.Sprintf("Invalid `docker ps` format: %v\n", err))
|
|
| 175 |
+ } |
|
| 176 |
+ |
|
| 177 |
+ for _, container := range containers {
|
|
| 178 |
+ containerCtx := &containerContext{
|
|
| 179 |
+ trunc: ctx.Trunc, |
|
| 180 |
+ c: container, |
|
| 181 |
+ } |
|
| 182 |
+ if err := tmpl.Execute(buffer, containerCtx); err != nil {
|
|
| 183 |
+ buffer = bytes.NewBufferString(fmt.Sprintf("Invalid `docker ps` format: %v\n", err))
|
|
| 184 |
+ break |
|
| 185 |
+ } |
|
| 186 |
+ if table && len(header) == 0 {
|
|
| 187 |
+ header = containerCtx.fullHeader() |
|
| 188 |
+ } |
|
| 189 |
+ buffer.WriteString("\n")
|
|
| 190 |
+ } |
|
| 191 |
+ |
|
| 192 |
+ if table {
|
|
| 193 |
+ t := tabwriter.NewWriter(ctx.Output, 20, 1, 3, ' ', 0) |
|
| 194 |
+ t.Write([]byte(header)) |
|
| 195 |
+ t.Write([]byte("\n"))
|
|
| 196 |
+ buffer.WriteTo(t) |
|
| 197 |
+ t.Flush() |
|
| 198 |
+ } else {
|
|
| 199 |
+ buffer.WriteTo(ctx.Output) |
|
| 200 |
+ } |
|
| 201 |
+} |
|
| 202 |
+ |
|
| 203 |
+func stripNamePrefix(ss []string) []string {
|
|
| 204 |
+ for i, s := range ss {
|
|
| 205 |
+ ss[i] = s[1:] |
|
| 206 |
+ } |
|
| 207 |
+ |
|
| 208 |
+ return ss |
|
| 209 |
+} |
| 0 | 210 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,68 @@ |
| 0 |
+package ps |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "testing" |
|
| 4 |
+ "time" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/docker/docker/api/types" |
|
| 7 |
+ "github.com/docker/docker/pkg/stringid" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+func TestContainerContextID(t *testing.T) {
|
|
| 11 |
+ containerId := stringid.GenerateRandomID() |
|
| 12 |
+ unix := time.Now().Unix() |
|
| 13 |
+ |
|
| 14 |
+ var ctx containerContext |
|
| 15 |
+ cases := []struct {
|
|
| 16 |
+ container types.Container |
|
| 17 |
+ trunc bool |
|
| 18 |
+ expValue string |
|
| 19 |
+ expHeader string |
|
| 20 |
+ call func() string |
|
| 21 |
+ }{
|
|
| 22 |
+ {types.Container{ID: containerId}, true, stringid.TruncateID(containerId), idHeader, ctx.ID},
|
|
| 23 |
+ {types.Container{Names: []string{"/foobar_baz"}}, true, "foobar_baz", namesHeader, ctx.Names},
|
|
| 24 |
+ {types.Container{Image: "ubuntu"}, true, "ubuntu", imageHeader, ctx.Image},
|
|
| 25 |
+ {types.Container{Image: ""}, true, "<no image>", imageHeader, ctx.Image},
|
|
| 26 |
+ {types.Container{Command: "sh -c 'ls -la'"}, true, `"sh -c 'ls -la'"`, commandHeader, ctx.Command},
|
|
| 27 |
+ {types.Container{Created: int(unix)}, true, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt},
|
|
| 28 |
+ {types.Container{Ports: []types.Port{types.Port{PrivatePort: 8080, PublicPort: 8080, Type: "tcp"}}}, true, "8080/tcp", portsHeader, ctx.Ports},
|
|
| 29 |
+ {types.Container{Status: "RUNNING"}, true, "RUNNING", statusHeader, ctx.Status},
|
|
| 30 |
+ {types.Container{SizeRw: 10}, true, "10 B", sizeHeader, ctx.Size},
|
|
| 31 |
+ {types.Container{SizeRw: 10, SizeRootFs: 20}, true, "10 B (virtual 20 B)", sizeHeader, ctx.Size},
|
|
| 32 |
+ {types.Container{Labels: map[string]string{"cpu": "6", "storage": "ssd"}}, true, "cpu=6,storage=ssd", labelsHeader, ctx.Labels},
|
|
| 33 |
+ } |
|
| 34 |
+ |
|
| 35 |
+ for _, c := range cases {
|
|
| 36 |
+ ctx = containerContext{c: c.container, trunc: c.trunc}
|
|
| 37 |
+ v := c.call() |
|
| 38 |
+ if v != c.expValue {
|
|
| 39 |
+ t.Fatalf("Expected %s, was %s\n", c.expValue, v)
|
|
| 40 |
+ } |
|
| 41 |
+ |
|
| 42 |
+ h := ctx.fullHeader() |
|
| 43 |
+ if h != c.expHeader {
|
|
| 44 |
+ t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
|
|
| 45 |
+ } |
|
| 46 |
+ } |
|
| 47 |
+ |
|
| 48 |
+ c := types.Container{Labels: map[string]string{"com.docker.swarm.swarm-id": "33", "com.docker.swarm.node_name": "ubuntu"}}
|
|
| 49 |
+ ctx = containerContext{c: c, trunc: true}
|
|
| 50 |
+ |
|
| 51 |
+ sid := ctx.Label("com.docker.swarm.swarm-id")
|
|
| 52 |
+ node := ctx.Label("com.docker.swarm.node_name")
|
|
| 53 |
+ if sid != "33" {
|
|
| 54 |
+ t.Fatal("Expected 33, was %s\n", sid)
|
|
| 55 |
+ } |
|
| 56 |
+ |
|
| 57 |
+ if node != "ubuntu" {
|
|
| 58 |
+ t.Fatal("Expected ubuntu, was %s\n", node)
|
|
| 59 |
+ } |
|
| 60 |
+ |
|
| 61 |
+ h := ctx.fullHeader() |
|
| 62 |
+ if h != "SWARM ID\tNODE NAME" {
|
|
| 63 |
+ t.Fatal("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h)
|
|
| 64 |
+ |
|
| 65 |
+ } |
|
| 66 |
+ |
|
| 67 |
+} |
| 0 | 68 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,65 @@ |
| 0 |
+package ps |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/docker/docker/api/types" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+const ( |
|
| 9 |
+ tableFormatKey = "table" |
|
| 10 |
+ rawFormatKey = "raw" |
|
| 11 |
+ |
|
| 12 |
+ defaultTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.RunningFor}} ago\t{{.Status}}\t{{.Ports}}\t{{.Names}}"
|
|
| 13 |
+ defaultQuietFormat = "{{.ID}}"
|
|
| 14 |
+) |
|
| 15 |
+ |
|
| 16 |
+type Context struct {
|
|
| 17 |
+ Output io.Writer |
|
| 18 |
+ Format string |
|
| 19 |
+ Size bool |
|
| 20 |
+ Quiet bool |
|
| 21 |
+ Trunc bool |
|
| 22 |
+} |
|
| 23 |
+ |
|
| 24 |
+func Format(ctx Context, containers []types.Container) {
|
|
| 25 |
+ switch ctx.Format {
|
|
| 26 |
+ case tableFormatKey: |
|
| 27 |
+ tableFormat(ctx, containers) |
|
| 28 |
+ case rawFormatKey: |
|
| 29 |
+ rawFormat(ctx, containers) |
|
| 30 |
+ default: |
|
| 31 |
+ customFormat(ctx, containers) |
|
| 32 |
+ } |
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+func rawFormat(ctx Context, containers []types.Container) {
|
|
| 36 |
+ if ctx.Quiet {
|
|
| 37 |
+ ctx.Format = `container_id: {{.ID}}`
|
|
| 38 |
+ } else {
|
|
| 39 |
+ ctx.Format = `container_id: {{.ID}}
|
|
| 40 |
+image: {{.Image}}
|
|
| 41 |
+command: {{.Command}}
|
|
| 42 |
+created_at: {{.CreatedAt}}
|
|
| 43 |
+status: {{.Status}}
|
|
| 44 |
+names: {{.Names}}
|
|
| 45 |
+labels: {{.Labels}}
|
|
| 46 |
+ports: {{.Ports}}
|
|
| 47 |
+` |
|
| 48 |
+ if ctx.Size {
|
|
| 49 |
+ ctx.Format += `size: {{.Size}}
|
|
| 50 |
+` |
|
| 51 |
+ } |
|
| 52 |
+ } |
|
| 53 |
+ |
|
| 54 |
+ customFormat(ctx, containers) |
|
| 55 |
+} |
|
| 56 |
+ |
|
| 57 |
+func tableFormat(ctx Context, containers []types.Container) {
|
|
| 58 |
+ ctx.Format = defaultTableFormat |
|
| 59 |
+ if ctx.Quiet {
|
|
| 60 |
+ ctx.Format = defaultQuietFormat |
|
| 61 |
+ } |
|
| 62 |
+ |
|
| 63 |
+ customFormat(ctx, containers) |
|
| 64 |
+} |
| ... | ... |
@@ -57,6 +57,7 @@ type AuthConfig struct {
|
| 57 | 57 |
type ConfigFile struct {
|
| 58 | 58 |
AuthConfigs map[string]AuthConfig `json:"auths"` |
| 59 | 59 |
HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"` |
| 60 |
+ PsFormat string `json:"psFormat,omitempty"` |
|
| 60 | 61 |
filename string // Note: not serialized - for internal use only |
| 61 | 62 |
} |
| 62 | 63 |
|
| ... | ... |
@@ -155,3 +155,34 @@ func TestNewJson(t *testing.T) {
|
| 155 | 155 |
t.Fatalf("Should have save in new form: %s", string(buf))
|
| 156 | 156 |
} |
| 157 | 157 |
} |
| 158 |
+ |
|
| 159 |
+func TestJsonWithPsFormat(t *testing.T) {
|
|
| 160 |
+ tmpHome, _ := ioutil.TempDir("", "config-test")
|
|
| 161 |
+ fn := filepath.Join(tmpHome, CONFIGFILE) |
|
| 162 |
+ js := `{
|
|
| 163 |
+ "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
|
|
| 164 |
+ "psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
|
|
| 165 |
+}` |
|
| 166 |
+ ioutil.WriteFile(fn, []byte(js), 0600) |
|
| 167 |
+ |
|
| 168 |
+ config, err := Load(tmpHome) |
|
| 169 |
+ if err != nil {
|
|
| 170 |
+ t.Fatalf("Failed loading on empty json file: %q", err)
|
|
| 171 |
+ } |
|
| 172 |
+ |
|
| 173 |
+ if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` {
|
|
| 174 |
+ t.Fatalf("Unknown ps format: %s\n", config.PsFormat)
|
|
| 175 |
+ } |
|
| 176 |
+ |
|
| 177 |
+ // Now save it and make sure it shows up in new form |
|
| 178 |
+ err = config.Save() |
|
| 179 |
+ if err != nil {
|
|
| 180 |
+ t.Fatalf("Failed to save: %q", err)
|
|
| 181 |
+ } |
|
| 182 |
+ |
|
| 183 |
+ buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE)) |
|
| 184 |
+ if !strings.Contains(string(buf), `"psFormat":`) || |
|
| 185 |
+ !strings.Contains(string(buf), "{{.ID}}") {
|
|
| 186 |
+ t.Fatalf("Should have save in new form: %s", string(buf))
|
|
| 187 |
+ } |
|
| 188 |
+} |
| ... | ... |
@@ -16,6 +16,7 @@ docker-ps - List containers |
| 16 | 16 |
[**-q**|**--quiet**[=*false*]] |
| 17 | 17 |
[**-s**|**--size**[=*false*]] |
| 18 | 18 |
[**--since**[=*SINCE*]] |
| 19 |
+[**-F**|**--format**=*"TEMPLATE"*] |
|
| 19 | 20 |
|
| 20 | 21 |
|
| 21 | 22 |
# DESCRIPTION |
| ... | ... |
@@ -59,6 +60,20 @@ the running containers. |
| 59 | 59 |
**--since**="" |
| 60 | 60 |
Show only containers created since Id or Name, include non-running ones. |
| 61 | 61 |
|
| 62 |
+**-F**, **--format**=*"TEMPLATE"* |
|
| 63 |
+ Pretty-print containers using a Go template. |
|
| 64 |
+ Valid placeholders: |
|
| 65 |
+ .ID - Container ID |
|
| 66 |
+ .Image - Image ID |
|
| 67 |
+ .Command - Quoted command |
|
| 68 |
+ .CreatedAt - Time when the container was created. |
|
| 69 |
+ .RunningFor - Elapsed time since the container was started. |
|
| 70 |
+ .Ports - Exposed ports. |
|
| 71 |
+ .Status - Container status. |
|
| 72 |
+ .Size - Container disk size. |
|
| 73 |
+ .Labels - All labels asigned to the container. |
|
| 74 |
+ .Label - Value of a specific label for this container. For example `{{.Label "com.docker.swarm.cpu"}}`
|
|
| 75 |
+ |
|
| 62 | 76 |
# EXAMPLES |
| 63 | 77 |
# Display all containers, including non-running |
| 64 | 78 |
|
| ... | ... |
@@ -82,6 +97,32 @@ the running containers. |
| 82 | 82 |
# docker ps -a -q --filter=name=determined_torvalds |
| 83 | 83 |
c1d3b0166030 |
| 84 | 84 |
|
| 85 |
+# Display containers with their commands |
|
| 86 |
+ |
|
| 87 |
+ # docker ps --format "{{.ID}}: {{.Command}}"
|
|
| 88 |
+ a87ecb4f327c: /bin/sh -c #(nop) MA |
|
| 89 |
+ 01946d9d34d8: /bin/sh -c #(nop) MA |
|
| 90 |
+ c1d3b0166030: /bin/sh -c yum -y up |
|
| 91 |
+ 41d50ecd2f57: /bin/sh -c #(nop) MA |
|
| 92 |
+ |
|
| 93 |
+# Display containers with their labels in a table |
|
| 94 |
+ |
|
| 95 |
+ # docker ps --format "table {{.ID}}\t{{.Labels}}"
|
|
| 96 |
+ CONTAINER ID LABELS |
|
| 97 |
+ a87ecb4f327c com.docker.swarm.node=ubuntu,com.docker.swarm.storage=ssd |
|
| 98 |
+ 01946d9d34d8 |
|
| 99 |
+ c1d3b0166030 com.docker.swarm.node=debian,com.docker.swarm.cpu=6 |
|
| 100 |
+ 41d50ecd2f57 com.docker.swarm.node=fedora,com.docker.swarm.cpu=3,com.docker.swarm.storage=ssd |
|
| 101 |
+ |
|
| 102 |
+# Display containers with their node label in a table |
|
| 103 |
+ |
|
| 104 |
+ # docker ps --format 'table {{.ID}}\t{{(.Label "com.docker.swarm.node")}}'
|
|
| 105 |
+ CONTAINER ID NODE |
|
| 106 |
+ a87ecb4f327c ubuntu |
|
| 107 |
+ 01946d9d34d8 |
|
| 108 |
+ c1d3b0166030 debian |
|
| 109 |
+ 41d50ecd2f57 fedora |
|
| 110 |
+ |
|
| 85 | 111 |
# HISTORY |
| 86 | 112 |
April 2014, Originally compiled by William Henry (whenry at redhat dot com) |
| 87 | 113 |
based on docker.com source material and internal work. |