Add --format
to docker service ls
This fix tries to improve the display of `docker service ls` and adds `--format` flag to `docker service ls`. In addition to `--format` flag, several other improvement: 1. Updates `docker stacks service`. 2. Adds `servicesFormat` to config file. Related docs has been updated. Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
This commit is contained in:
parent
354bd4aadd
commit
000f0403d9
8 changed files with 400 additions and 75 deletions
|
@ -5,9 +5,11 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
distreference "github.com/docker/distribution/reference"
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli/command/inspect"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
units "github.com/docker/go-units"
|
||||
)
|
||||
|
||||
|
@ -327,3 +329,93 @@ func (ctx *serviceInspectContext) EndpointMode() string {
|
|||
func (ctx *serviceInspectContext) Ports() []swarm.PortConfig {
|
||||
return ctx.Service.Endpoint.Ports
|
||||
}
|
||||
|
||||
const (
|
||||
defaultServiceTableFormat = "table {{.ID}}\t{{.Name}}\t{{.Mode}}\t{{.Replicas}}\t{{.Image}}"
|
||||
|
||||
serviceIDHeader = "ID"
|
||||
modeHeader = "MODE"
|
||||
replicasHeader = "REPLICAS"
|
||||
)
|
||||
|
||||
// NewServiceListFormat returns a Format for rendering using a service Context
|
||||
func NewServiceListFormat(source string, quiet bool) Format {
|
||||
switch source {
|
||||
case TableFormatKey:
|
||||
if quiet {
|
||||
return defaultQuietFormat
|
||||
}
|
||||
return defaultServiceTableFormat
|
||||
case RawFormatKey:
|
||||
if quiet {
|
||||
return `id: {{.ID}}`
|
||||
}
|
||||
return `id: {{.ID}}\nname: {{.Name}}\nmode: {{.Mode}}\nreplicas: {{.Replicas}}\nimage: {{.Image}}\n`
|
||||
}
|
||||
return Format(source)
|
||||
}
|
||||
|
||||
// ServiceListInfo stores the information about mode and replicas to be used by template
|
||||
type ServiceListInfo struct {
|
||||
Mode string
|
||||
Replicas string
|
||||
}
|
||||
|
||||
// ServiceListWrite writes the context
|
||||
func ServiceListWrite(ctx Context, services []swarm.Service, info map[string]ServiceListInfo) error {
|
||||
render := func(format func(subContext subContext) error) error {
|
||||
for _, service := range services {
|
||||
serviceCtx := &serviceContext{service: service, mode: info[service.ID].Mode, replicas: info[service.ID].Replicas}
|
||||
if err := format(serviceCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return ctx.Write(&serviceContext{}, render)
|
||||
}
|
||||
|
||||
type serviceContext struct {
|
||||
HeaderContext
|
||||
service swarm.Service
|
||||
mode string
|
||||
replicas string
|
||||
}
|
||||
|
||||
func (c *serviceContext) MarshalJSON() ([]byte, error) {
|
||||
return marshalJSON(c)
|
||||
}
|
||||
|
||||
func (c *serviceContext) ID() string {
|
||||
c.AddHeader(serviceIDHeader)
|
||||
return stringid.TruncateID(c.service.ID)
|
||||
}
|
||||
|
||||
func (c *serviceContext) Name() string {
|
||||
c.AddHeader(nameHeader)
|
||||
return c.service.Spec.Name
|
||||
}
|
||||
|
||||
func (c *serviceContext) Mode() string {
|
||||
c.AddHeader(modeHeader)
|
||||
return c.mode
|
||||
}
|
||||
|
||||
func (c *serviceContext) Replicas() string {
|
||||
c.AddHeader(replicasHeader)
|
||||
return c.replicas
|
||||
}
|
||||
|
||||
func (c *serviceContext) Image() string {
|
||||
c.AddHeader(imageHeader)
|
||||
image := c.service.Spec.TaskTemplate.ContainerSpec.Image
|
||||
if ref, err := distreference.ParseNamed(image); err == nil {
|
||||
// update image string for display
|
||||
namedTagged, ok := ref.(distreference.NamedTagged)
|
||||
if ok {
|
||||
image = namedTagged.Name() + ":" + namedTagged.Tag()
|
||||
}
|
||||
}
|
||||
|
||||
return image
|
||||
}
|
||||
|
|
177
cli/command/formatter/service_test.go
Normal file
177
cli/command/formatter/service_test.go
Normal file
|
@ -0,0 +1,177 @@
|
|||
package formatter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
)
|
||||
|
||||
func TestServiceContextWrite(t *testing.T) {
|
||||
cases := []struct {
|
||||
context Context
|
||||
expected string
|
||||
}{
|
||||
// Errors
|
||||
{
|
||||
Context{Format: "{{InvalidFunction}}"},
|
||||
`Template parsing error: template: :1: function "InvalidFunction" not defined
|
||||
`,
|
||||
},
|
||||
{
|
||||
Context{Format: "{{nil}}"},
|
||||
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
|
||||
`,
|
||||
},
|
||||
// Table format
|
||||
{
|
||||
Context{Format: NewServiceListFormat("table", false)},
|
||||
`ID NAME MODE REPLICAS IMAGE
|
||||
id_baz baz global 2/4
|
||||
id_bar bar replicated 2/4
|
||||
`,
|
||||
},
|
||||
{
|
||||
Context{Format: NewServiceListFormat("table", true)},
|
||||
`id_baz
|
||||
id_bar
|
||||
`,
|
||||
},
|
||||
{
|
||||
Context{Format: NewServiceListFormat("table {{.Name}}", false)},
|
||||
`NAME
|
||||
baz
|
||||
bar
|
||||
`,
|
||||
},
|
||||
{
|
||||
Context{Format: NewServiceListFormat("table {{.Name}}", true)},
|
||||
`NAME
|
||||
baz
|
||||
bar
|
||||
`,
|
||||
},
|
||||
// Raw Format
|
||||
{
|
||||
Context{Format: NewServiceListFormat("raw", false)},
|
||||
`id: id_baz
|
||||
name: baz
|
||||
mode: global
|
||||
replicas: 2/4
|
||||
image:
|
||||
|
||||
id: id_bar
|
||||
name: bar
|
||||
mode: replicated
|
||||
replicas: 2/4
|
||||
image:
|
||||
|
||||
`,
|
||||
},
|
||||
{
|
||||
Context{Format: NewServiceListFormat("raw", true)},
|
||||
`id: id_baz
|
||||
id: id_bar
|
||||
`,
|
||||
},
|
||||
// Custom Format
|
||||
{
|
||||
Context{Format: NewServiceListFormat("{{.Name}}", false)},
|
||||
`baz
|
||||
bar
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range cases {
|
||||
services := []swarm.Service{
|
||||
{ID: "id_baz", Spec: swarm.ServiceSpec{Annotations: swarm.Annotations{Name: "baz"}}},
|
||||
{ID: "id_bar", Spec: swarm.ServiceSpec{Annotations: swarm.Annotations{Name: "bar"}}},
|
||||
}
|
||||
info := map[string]ServiceListInfo{
|
||||
"id_baz": {
|
||||
Mode: "global",
|
||||
Replicas: "2/4",
|
||||
},
|
||||
"id_bar": {
|
||||
Mode: "replicated",
|
||||
Replicas: "2/4",
|
||||
},
|
||||
}
|
||||
out := bytes.NewBufferString("")
|
||||
testcase.context.Output = out
|
||||
err := ServiceListWrite(testcase.context, services, info)
|
||||
if err != nil {
|
||||
assert.Error(t, err, testcase.expected)
|
||||
} else {
|
||||
assert.Equal(t, out.String(), testcase.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceContextWriteJSON(t *testing.T) {
|
||||
services := []swarm.Service{
|
||||
{ID: "id_baz", Spec: swarm.ServiceSpec{Annotations: swarm.Annotations{Name: "baz"}}},
|
||||
{ID: "id_bar", Spec: swarm.ServiceSpec{Annotations: swarm.Annotations{Name: "bar"}}},
|
||||
}
|
||||
info := map[string]ServiceListInfo{
|
||||
"id_baz": {
|
||||
Mode: "global",
|
||||
Replicas: "2/4",
|
||||
},
|
||||
"id_bar": {
|
||||
Mode: "replicated",
|
||||
Replicas: "2/4",
|
||||
},
|
||||
}
|
||||
expectedJSONs := []map[string]interface{}{
|
||||
{"ID": "id_baz", "Name": "baz", "Mode": "global", "Replicas": "2/4", "Image": ""},
|
||||
{"ID": "id_bar", "Name": "bar", "Mode": "replicated", "Replicas": "2/4", "Image": ""},
|
||||
}
|
||||
|
||||
out := bytes.NewBufferString("")
|
||||
err := ServiceListWrite(Context{Format: "{{json .}}", Output: out}, services, info)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
|
||||
t.Logf("Output: line %d: %s", i, line)
|
||||
var m map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(line), &m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.DeepEqual(t, m, expectedJSONs[i])
|
||||
}
|
||||
}
|
||||
func TestServiceContextWriteJSONField(t *testing.T) {
|
||||
services := []swarm.Service{
|
||||
{ID: "id_baz", Spec: swarm.ServiceSpec{Annotations: swarm.Annotations{Name: "baz"}}},
|
||||
{ID: "id_bar", Spec: swarm.ServiceSpec{Annotations: swarm.Annotations{Name: "bar"}}},
|
||||
}
|
||||
info := map[string]ServiceListInfo{
|
||||
"id_baz": {
|
||||
Mode: "global",
|
||||
Replicas: "2/4",
|
||||
},
|
||||
"id_bar": {
|
||||
Mode: "replicated",
|
||||
Replicas: "2/4",
|
||||
},
|
||||
}
|
||||
out := bytes.NewBufferString("")
|
||||
err := ServiceListWrite(Context{Format: "{{json .Name}}", Output: out}, services, info)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
|
||||
t.Logf("Output: line %d: %s", i, line)
|
||||
var s string
|
||||
if err := json.Unmarshal([]byte(line), &s); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, s, services[i].Spec.Name)
|
||||
}
|
||||
}
|
|
@ -2,27 +2,21 @@ package service
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"text/tabwriter"
|
||||
|
||||
distreference "github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cli/command"
|
||||
"github.com/docker/docker/cli/command/formatter"
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
listItemFmt = "%s\t%s\t%s\t%s\t%s\n"
|
||||
)
|
||||
|
||||
type listOptions struct {
|
||||
quiet bool
|
||||
format string
|
||||
filter opts.FilterOpt
|
||||
}
|
||||
|
||||
|
@ -41,6 +35,7 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
|
||||
flags.StringVar(&opts.format, "format", "", "Pretty-print services using a Go template")
|
||||
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
|
||||
|
||||
return cmd
|
||||
|
@ -49,13 +44,13 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
func runList(dockerCli *command.DockerCli, opts listOptions) error {
|
||||
ctx := context.Background()
|
||||
client := dockerCli.Client()
|
||||
out := dockerCli.Out()
|
||||
|
||||
services, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: opts.filter.Value()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info := map[string]formatter.ServiceListInfo{}
|
||||
if len(services) > 0 && !opts.quiet {
|
||||
// only non-empty services and not quiet, should we call TaskList and NodeList api
|
||||
taskFilter := filters.NewArgs()
|
||||
|
@ -73,20 +68,30 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error {
|
|||
return err
|
||||
}
|
||||
|
||||
PrintNotQuiet(out, services, nodes, tasks)
|
||||
} else if !opts.quiet {
|
||||
// no services and not quiet, print only one line with columns ID, NAME, MODE, REPLICAS...
|
||||
PrintNotQuiet(out, services, []swarm.Node{}, []swarm.Task{})
|
||||
} else {
|
||||
PrintQuiet(out, services)
|
||||
info = GetServicesStatus(services, nodes, tasks)
|
||||
}
|
||||
|
||||
return nil
|
||||
format := opts.format
|
||||
if len(format) == 0 {
|
||||
if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !opts.quiet {
|
||||
format = dockerCli.ConfigFile().ServicesFormat
|
||||
} else {
|
||||
format = formatter.TableFormatKey
|
||||
}
|
||||
}
|
||||
|
||||
servicesCtx := formatter.Context{
|
||||
Output: dockerCli.Out(),
|
||||
Format: formatter.NewServiceListFormat(format, opts.quiet),
|
||||
}
|
||||
return formatter.ServiceListWrite(servicesCtx, services, info)
|
||||
}
|
||||
|
||||
// PrintNotQuiet shows service list in a non-quiet way.
|
||||
// Besides this, command `docker stack services xxx` will call this, too.
|
||||
func PrintNotQuiet(out io.Writer, services []swarm.Service, nodes []swarm.Node, tasks []swarm.Task) {
|
||||
// GetServicesStatus returns a map of mode and replicas
|
||||
func GetServicesStatus(services []swarm.Service, nodes []swarm.Node, tasks []swarm.Task) map[string]formatter.ServiceListInfo {
|
||||
running := map[string]int{}
|
||||
tasksNoShutdown := map[string]int{}
|
||||
|
||||
activeNodes := make(map[string]struct{})
|
||||
for _, n := range nodes {
|
||||
if n.Status.State != swarm.NodeStateDown {
|
||||
|
@ -94,9 +99,6 @@ func PrintNotQuiet(out io.Writer, services []swarm.Service, nodes []swarm.Node,
|
|||
}
|
||||
}
|
||||
|
||||
running := map[string]int{}
|
||||
tasksNoShutdown := map[string]int{}
|
||||
|
||||
for _, task := range tasks {
|
||||
if task.DesiredState != swarm.TaskStateShutdown {
|
||||
tasksNoShutdown[task.ServiceID]++
|
||||
|
@ -107,52 +109,20 @@ func PrintNotQuiet(out io.Writer, services []swarm.Service, nodes []swarm.Node,
|
|||
}
|
||||
}
|
||||
|
||||
printTable(out, services, running, tasksNoShutdown)
|
||||
}
|
||||
|
||||
func printTable(out io.Writer, services []swarm.Service, running, tasksNoShutdown map[string]int) {
|
||||
writer := tabwriter.NewWriter(out, 0, 4, 2, ' ', 0)
|
||||
|
||||
// Ignore flushing errors
|
||||
defer writer.Flush()
|
||||
|
||||
fmt.Fprintf(writer, listItemFmt, "ID", "NAME", "MODE", "REPLICAS", "IMAGE")
|
||||
|
||||
info := map[string]formatter.ServiceListInfo{}
|
||||
for _, service := range services {
|
||||
mode := ""
|
||||
replicas := ""
|
||||
info[service.ID] = formatter.ServiceListInfo{}
|
||||
if service.Spec.Mode.Replicated != nil && service.Spec.Mode.Replicated.Replicas != nil {
|
||||
mode = "replicated"
|
||||
replicas = fmt.Sprintf("%d/%d", running[service.ID], *service.Spec.Mode.Replicated.Replicas)
|
||||
info[service.ID] = formatter.ServiceListInfo{
|
||||
Mode: "replicated",
|
||||
Replicas: fmt.Sprintf("%d/%d", running[service.ID], *service.Spec.Mode.Replicated.Replicas),
|
||||
}
|
||||
} else if service.Spec.Mode.Global != nil {
|
||||
mode = "global"
|
||||
replicas = fmt.Sprintf("%d/%d", running[service.ID], tasksNoShutdown[service.ID])
|
||||
}
|
||||
image := service.Spec.TaskTemplate.ContainerSpec.Image
|
||||
ref, err := distreference.ParseNamed(image)
|
||||
if err == nil {
|
||||
// update image string for display
|
||||
namedTagged, ok := ref.(distreference.NamedTagged)
|
||||
if ok {
|
||||
image = namedTagged.Name() + ":" + namedTagged.Tag()
|
||||
info[service.ID] = formatter.ServiceListInfo{
|
||||
Mode: "global",
|
||||
Replicas: fmt.Sprintf("%d/%d", running[service.ID], tasksNoShutdown[service.ID]),
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(
|
||||
writer,
|
||||
listItemFmt,
|
||||
stringid.TruncateID(service.ID),
|
||||
service.Spec.Name,
|
||||
mode,
|
||||
replicas,
|
||||
image)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintQuiet shows service list in a quiet way.
|
||||
// Besides this, command `docker stack services xxx` will call this, too.
|
||||
func PrintQuiet(out io.Writer, services []swarm.Service) {
|
||||
for _, service := range services {
|
||||
fmt.Fprintln(out, service.ID)
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cli/command"
|
||||
"github.com/docker/docker/cli/command/formatter"
|
||||
"github.com/docker/docker/cli/command/service"
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -16,6 +17,7 @@ import (
|
|||
|
||||
type servicesOptions struct {
|
||||
quiet bool
|
||||
format string
|
||||
filter opts.FilterOpt
|
||||
namespace string
|
||||
}
|
||||
|
@ -34,6 +36,7 @@ func newServicesCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
|
||||
flags.StringVar(&opts.format, "format", "", "Pretty-print services using a Go template")
|
||||
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
|
||||
|
||||
return cmd
|
||||
|
@ -57,9 +60,8 @@ func runServices(dockerCli *command.DockerCli, opts servicesOptions) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
if opts.quiet {
|
||||
service.PrintQuiet(out, services)
|
||||
} else {
|
||||
info := map[string]formatter.ServiceListInfo{}
|
||||
if !opts.quiet {
|
||||
taskFilter := filters.NewArgs()
|
||||
for _, service := range services {
|
||||
taskFilter.Add("service", service.ID)
|
||||
|
@ -69,11 +71,27 @@ func runServices(dockerCli *command.DockerCli, opts servicesOptions) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nodes, err := client.NodeList(ctx, types.NodeListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service.PrintNotQuiet(out, services, nodes, tasks)
|
||||
|
||||
info = service.GetServicesStatus(services, nodes, tasks)
|
||||
}
|
||||
return nil
|
||||
|
||||
format := opts.format
|
||||
if len(format) == 0 {
|
||||
if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !opts.quiet {
|
||||
format = dockerCli.ConfigFile().ServicesFormat
|
||||
} else {
|
||||
format = formatter.TableFormatKey
|
||||
}
|
||||
}
|
||||
|
||||
servicesCtx := formatter.Context{
|
||||
Output: dockerCli.Out(),
|
||||
Format: formatter.NewServiceListFormat(format, opts.quiet),
|
||||
}
|
||||
return formatter.ServiceListWrite(servicesCtx, services, info)
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ type ConfigFile struct {
|
|||
CredentialHelpers map[string]string `json:"credHelpers,omitempty"`
|
||||
Filename string `json:"-"` // Note: for internal use only
|
||||
ServiceInspectFormat string `json:"serviceInspectFormat,omitempty"`
|
||||
ServicesFormat string `json:"servicesFormat,omitempty"`
|
||||
}
|
||||
|
||||
// LegacyLoadFromReader reads the non-nested configuration data given and sets up the
|
||||
|
|
|
@ -137,6 +137,13 @@ Docker's client uses this property. If this property is not set, the client
|
|||
falls back to the default table format. For a list of supported formatting
|
||||
directives, see the [**Formatting** section in the `docker plugin ls` documentation](plugin_ls.md)
|
||||
|
||||
The property `servicesFormat` specifies the default format for `docker
|
||||
service ls` output. When the `--format` flag is not provided with the
|
||||
`docker service ls` command, Docker's client uses this property. If this
|
||||
property is not set, the client falls back to the default json format. For a
|
||||
list of supported formatting directives, see the
|
||||
[**Formatting** section in the `docker service ls` documentation](service_ls.md)
|
||||
|
||||
The property `serviceInspectFormat` specifies the default format for `docker
|
||||
service inspect` output. When the `--format` flag is not provided with the
|
||||
`docker service inspect` command, Docker's client uses this property. If this
|
||||
|
@ -194,6 +201,7 @@ Following is a sample `config.json` file:
|
|||
"imagesFormat": "table {{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}",
|
||||
"pluginsFormat": "table {{.ID}}\t{{.Name}}\t{{.Enabled}}",
|
||||
"statsFormat": "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}",
|
||||
"servicesFormat": "table {{.ID}}\t{{.Name}}\t{{.Mode}}",
|
||||
"serviceInspectFormat": "pretty",
|
||||
"detachKeys": "ctrl-e,e",
|
||||
"credsStore": "secretservice",
|
||||
|
|
|
@ -24,9 +24,10 @@ Aliases:
|
|||
ls, list
|
||||
|
||||
Options:
|
||||
-f, --filter value Filter output based on conditions provided
|
||||
--help Print usage
|
||||
-q, --quiet Only display IDs
|
||||
-f, --filter filter Filter output based on conditions provided
|
||||
--format string Pretty-print services using a Go template
|
||||
--help Print usage
|
||||
-q, --quiet Only display IDs
|
||||
```
|
||||
|
||||
This command when run targeting a manager, lists services are running in the
|
||||
|
@ -103,6 +104,34 @@ ID NAME MODE REPLICAS IMAGE
|
|||
0bcjwfh8ychr redis replicated 1/1 redis:3.0.6
|
||||
```
|
||||
|
||||
## Formatting
|
||||
|
||||
The formatting options (`--format`) pretty-prints services output
|
||||
using a Go template.
|
||||
|
||||
Valid placeholders for the Go template are listed below:
|
||||
|
||||
Placeholder | Description
|
||||
------------|------------------------------------------------------------------------------------------
|
||||
`.ID` | Service ID
|
||||
`.Name` | Service name
|
||||
`.Mode` | Service mode (replicated, global)
|
||||
`.Replicas` | Service replicas
|
||||
`.Image` | Service image
|
||||
|
||||
When using the `--format` option, the `service ls` command will either
|
||||
output the data exactly as the template declares or, when using the
|
||||
`table` directive, includes column headers as well.
|
||||
|
||||
The following example uses a template without headers and outputs the
|
||||
`ID`, `Mode`, and `Replicas` entries separated by a colon for all services:
|
||||
|
||||
```bash
|
||||
$ docker service ls --format "{{.ID}}: {{.Mode}} {{.Replicas}}"
|
||||
0zmvwuiu3vue: replicated 10/10
|
||||
fm6uf97exkul: global 5/5
|
||||
```
|
||||
|
||||
## Related information
|
||||
|
||||
* [service create](service_create.md)
|
||||
|
|
|
@ -22,9 +22,10 @@ Usage: docker stack services [OPTIONS] STACK
|
|||
List the services in the stack
|
||||
|
||||
Options:
|
||||
-f, --filter value Filter output based on conditions provided
|
||||
--help Print usage
|
||||
-q, --quiet Only display IDs
|
||||
-f, --filter filter Filter output based on conditions provided
|
||||
--format string Pretty-print services using a Go template
|
||||
--help Print usage
|
||||
-q, --quiet Only display IDs
|
||||
```
|
||||
|
||||
Lists the services that are running as part of the specified stack. This
|
||||
|
@ -62,6 +63,35 @@ The currently supported filters are:
|
|||
* name (`--filter name=myapp_web`)
|
||||
* label (`--filter label=key=value`)
|
||||
|
||||
## Formatting
|
||||
|
||||
The formatting options (`--format`) pretty-prints services output
|
||||
using a Go template.
|
||||
|
||||
Valid placeholders for the Go template are listed below:
|
||||
|
||||
Placeholder | Description
|
||||
------------|------------------------------------------------------------------------------------------
|
||||
`.ID` | Service ID
|
||||
`.Name` | Service name
|
||||
`.Mode` | Service mode (replicated, global)
|
||||
`.Replicas` | Service replicas
|
||||
`.Image` | Service image
|
||||
|
||||
When using the `--format` option, the `stack services` command will either
|
||||
output the data exactly as the template declares or, when using the
|
||||
`table` directive, includes column headers as well.
|
||||
|
||||
The following example uses a template without headers and outputs the
|
||||
`ID`, `Mode`, and `Replicas` entries separated by a colon for all services:
|
||||
|
||||
```bash
|
||||
$ docker stack services --format "{{.ID}}: {{.Mode}} {{.Replicas}}"
|
||||
0zmvwuiu3vue: replicated 10/10
|
||||
fm6uf97exkul: global 5/5
|
||||
```
|
||||
|
||||
|
||||
## Related information
|
||||
|
||||
* [stack deploy](stack_deploy.md)
|
||||
|
|
Loading…
Reference in a new issue