The --format flag table directive does not work with docker inspect
docker service inspect --format 'table {{.Spec.Name}}\t{{range .Endpoint.VirtualIPs}}{{.Addr}} {{end}}' $(docker service ls -q)
This is not printing a table, it's printing:
container1\t10.0.2.3 107.2.30.4
container2\t10.0.2.5 107.2.30.8
...etc...
Steps to reproduce the issue:
- Deploy a container as a service:
docker service create ngnixwill work - Run command:
docker service inspect --format 'table {{.Spec.Name}}\t{{range .Endpoint.VirtualIPs}}{{.Addr}} {{end}}' $(docker service ls -q)
Describe the results you received:
table sharp_goldstine\t
*Note service has no IP address, that's fine here
Describe the results you expected:
Table output similar to docker stats with 2 columns
Output of docker version:
Client:
Version: 18.03.1-ce
API version: 1.37
Go version: go1.9.5
Git commit: 9ee9f40
Built: Thu Apr 26 07:13:02 2018
OS/Arch: darwin/amd64
Experimental: false
Orchestrator: swarm
Server:
Engine:
Version: 18.03.1-ce
API version: 1.37 (minimum version 1.12)
Go version: go1.9.5
Git commit: 9ee9f40
Built: Thu Apr 26 07:22:38 2018
OS/Arch: linux/amd64
Experimental: true
Output of docker info:
Containers: 16
Running: 9
Paused: 0
Stopped: 7
Images: 49
Server Version: 18.03.1-ce
Storage Driver: overlay2
Backing Filesystem: extfs
Supports d_type: true
Native Overlay Diff: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
Volume: local
Network: bridge host ipvlan macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file logentries splunk syslog
Swarm: active
NodeID: daldy4p8ynukd4mlf9p1wv1vx
Is Manager: true
ClusterID: hccuyor98zeifze8mexdc4uw0
Managers: 1
Nodes: 1
Orchestration:
Task History Retention Limit: 5
Raft:
Snapshot Interval: 10000
Number of Old Snapshots to Retain: 0
Heartbeat Tick: 1
Election Tick: 3
Dispatcher:
Heartbeat Period: 5 seconds
CA Configuration:
Expiry Duration: 3 months
Force Rotate: 0
Autolock Managers: false
Root Rotation In Progress: false
Node Address: 192.168.65.2
Manager Addresses:
192.168.65.2:2377
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 773c489c9c1b21a6d78b5c538cd395416ec50f88
runc version: 4fc53a81fb7c994640722ac585fa9ca548971871
init version: 949e6fa
Security Options:
seccomp
Profile: default
Kernel Version: 4.9.87-linuxkit-aufs
Operating System: Docker for Mac
OSType: linux
Architecture: x86_64
CPUs: 4
Total Memory: 1.952GiB
Name: linuxkit-025000000001
ID: TXEI:TAOU:SCOP:BGAB:6KTA:3TZ3:46QJ:GORK:45NX:3K7J:MHFC:YXNY
Docker Root Dir: /var/lib/docker
Debug Mode (client): false
Debug Mode (server): true
File Descriptors: 106
Goroutines: 243
System Time: 2018-06-01T11:40:48.2385353Z
EventsListeners: 7
HTTP Proxy: docker.for.mac.http.internal:3128
HTTPS Proxy: docker.for.mac.http.internal:3129
Registry: https://index.docker.io/v1/
Labels:
Experimental: true
Insecure Registries:
127.0.0.0/8
Live Restore Enabled: false
Additional environment details (AWS, VirtualBox, physical, etc.):
Mac laptop
TLDR; docker inspect deals with JSON output and thus JSON output templates, versus the formatting strings you'd expect from other commands (such as docker stats
I think the confusion stems from docker inspect not having the same --format functionality as other traditional command such as docker stats which you mentioned. When formatting command output commands provide a format context and a writer. Stats for example:
func NewStatsFormat(source, osType string) Format {
if source == TableFormatKey {
if osType == winOSType {
return Format(winDefaultStatsTableFormat)
}
return Format(defaultStatsTableFormat)
}
return Format(source)
}
https://github.com/docker/cli/blob/master/cli/command/formatter/stats.go#L102
So based on various constraints it either returns a default format string (if you just pass in table as the format string for example) or just passes the format string provided as-is. Then you have the writer which renders the template given the format:
func ContainerStatsWrite(ctx Context, containerStats []StatsEntry, osType string, trunc bool) error {
render := func(format func(subContext subContext) error) error {
for _, cstats := range containerStats {
containerStatsCtx := &containerStatsContext{
s: cstats,
os: osType,
trunc: trunc,
}
if err := format(containerStatsCtx); err != nil {
return err
}
}
return nil
}
memUsage := memUseHeader
if osType == winOSType {
memUsage = winMemUseHeader
}
containerStatsCtx := containerStatsContext{}
containerStatsCtx.header = map[string]string{
"Container": containerHeader,
"Name": nameHeader,
"ID": containerIDHeader,
"CPUPerc": cpuPercHeader,
"MemUsage": memUsage,
"MemPerc": memPercHeader,
"NetIO": netIOHeader,
"BlockIO": blockIOHeader,
"PIDs": pidsHeader,
}
containerStatsCtx.os = osType
return ctx.Write(&containerStatsCtx, render)
}
https://github.com/docker/cli/blob/master/cli/command/formatter/stats.go#L118
Now with the inspect command it takes a different path:
// ServiceInspectWrite renders the context for a list of services
func ServiceInspectWrite(ctx Context, refs []string, getRef, getNetwork inspect.GetRefFunc) error {
if ctx.Format != serviceInspectPrettyTemplate {
return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef)
}
https://github.com/docker/cli/blob/master/cli/command/formatter/service.go#L170
Unless you have pretty format enabled it does an inspect.Inspect call:
// NewTemplateInspectorFromString creates a new TemplateInspector from a string
// which is compiled into a template.
func NewTemplateInspectorFromString(out io.Writer, tmplStr string) (Inspector, error) {
if tmplStr == "" {
return NewIndentedInspector(out), nil
}
tmpl, err := templates.Parse(tmplStr)
if err != nil {
return nil, errors.Errorf("Template parsing error: %s", err)
}
return NewTemplateInspector(out, tmpl), nil
}
// -------- SNIP ------------
func Inspect(out io.Writer, references []string, tmplStr string, getRef GetRefFunc) error {
inspector, err := NewTemplateInspectorFromString(out, tmplStr)
This in turn uses a different string format including different base functions:
var basicFunctions = template.FuncMap{
"json": func(v interface{}) string {
buf := &bytes.Buffer{}
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
enc.Encode(v)
// Remove the trailing new line added by the encoder
return strings.TrimSpace(buf.String())
},
"split": strings.Split,
"join": strings.Join,
"title": strings.Title,
"lower": strings.ToLower,
"upper": strings.ToUpper,
"pad": padWithSpace,
"truncate": truncateWithLength,
}
https://github.com/docker/cli/blob/master/templates/templates.go#L12
Which is essentially why table isn't working as expected and coming back as raw. That said it might be good to have a --template-format instead to make it a bit less confusing.