Add Engine version to docker node ls

This adds the Engine version to `docker node ls`, and `.EngineVersion` as a
template option.

With this patch applied:

    docker node ls
    ID                            HOSTNAME                STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
    wp9231itoqsh4rqceojqo01vp *   linuxkit-025000000001   Ready               Active              Leader              18.01.0-ce

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2018-01-29 13:44:26 -08:00
parent a0044ba3a7
commit e888dd711f
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
6 changed files with 35 additions and 19 deletions

View File

@ -14,7 +14,7 @@ import (
) )
const ( const (
defaultNodeTableFormat = "table {{.ID}} {{if .Self}}*{{else}} {{ end }}\t{{.Hostname}}\t{{.Status}}\t{{.Availability}}\t{{.ManagerStatus}}" defaultNodeTableFormat = "table {{.ID}} {{if .Self}}*{{else}} {{ end }}\t{{.Hostname}}\t{{.Status}}\t{{.Availability}}\t{{.ManagerStatus}}\t{{.EngineVersion}}"
nodeInspectPrettyTemplate Format = `ID: {{.ID}} nodeInspectPrettyTemplate Format = `ID: {{.ID}}
{{- if .Name }} {{- if .Name }}
Name: {{.Name}} Name: {{.Name}}
@ -75,6 +75,7 @@ TLS Info:
hostnameHeader = "HOSTNAME" hostnameHeader = "HOSTNAME"
availabilityHeader = "AVAILABILITY" availabilityHeader = "AVAILABILITY"
managerStatusHeader = "MANAGER STATUS" managerStatusHeader = "MANAGER STATUS"
engineVersionHeader = "ENGINE VERSION"
tlsStatusHeader = "TLS STATUS" tlsStatusHeader = "TLS STATUS"
) )
@ -115,6 +116,7 @@ func NodeWrite(ctx Context, nodes []swarm.Node, info types.Info) error {
"Status": statusHeader, "Status": statusHeader,
"Availability": availabilityHeader, "Availability": availabilityHeader,
"ManagerStatus": managerStatusHeader, "ManagerStatus": managerStatusHeader,
"EngineVersion": engineVersionHeader,
"TLSStatus": tlsStatusHeader, "TLSStatus": tlsStatusHeader,
} }
nodeCtx := nodeContext{} nodeCtx := nodeContext{}
@ -176,6 +178,10 @@ func (c *nodeContext) TLSStatus() string {
return "Needs Rotation" return "Needs Rotation"
} }
func (c *nodeContext) EngineVersion() string {
return c.n.Description.Engine.EngineVersion
}
// NodeInspectWrite renders the context for a list of nodes // NodeInspectWrite renders the context for a list of nodes
func NodeInspectWrite(ctx Context, refs []string, getRef inspect.GetRefFunc) error { func NodeInspectWrite(ctx Context, refs []string, getRef inspect.GetRefFunc) error {
if ctx.Format != nodeInspectPrettyTemplate { if ctx.Format != nodeInspectPrettyTemplate {

View File

@ -74,10 +74,10 @@ func TestNodeContextWrite(t *testing.T) {
// Table format // Table format
{ {
context: Context{Format: NewNodeFormat("table", false)}, context: Context{Format: NewNodeFormat("table", false)},
expected: `ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS expected: `ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
nodeID1 foobar_baz Foo Drain Leader nodeID1 foobar_baz Foo Drain Leader 18.03.0-ce
nodeID2 foobar_bar Bar Active Reachable nodeID2 foobar_bar Bar Active Reachable 1.2.3
nodeID3 foobar_boo Boo Active ` + "\n", // (to preserve whitespace) nodeID3 foobar_boo Boo Active ` + "\n", // (to preserve whitespace)
clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
}, },
{ {
@ -172,6 +172,7 @@ foobar_boo Unknown
Description: swarm.NodeDescription{ Description: swarm.NodeDescription{
Hostname: "foobar_baz", Hostname: "foobar_baz",
TLSInfo: swarm.TLSInfo{TrustRoot: "no"}, TLSInfo: swarm.TLSInfo{TrustRoot: "no"},
Engine: swarm.EngineDescription{EngineVersion: "18.03.0-ce"},
}, },
Status: swarm.NodeStatus{State: swarm.NodeState("foo")}, Status: swarm.NodeStatus{State: swarm.NodeState("foo")},
Spec: swarm.NodeSpec{Availability: swarm.NodeAvailability("drain")}, Spec: swarm.NodeSpec{Availability: swarm.NodeAvailability("drain")},
@ -182,6 +183,7 @@ foobar_boo Unknown
Description: swarm.NodeDescription{ Description: swarm.NodeDescription{
Hostname: "foobar_bar", Hostname: "foobar_bar",
TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}, TLSInfo: swarm.TLSInfo{TrustRoot: "hi"},
Engine: swarm.EngineDescription{EngineVersion: "1.2.3"},
}, },
Status: swarm.NodeStatus{State: swarm.NodeState("bar")}, Status: swarm.NodeStatus{State: swarm.NodeState("bar")},
Spec: swarm.NodeSpec{Availability: swarm.NodeAvailability("active")}, Spec: swarm.NodeSpec{Availability: swarm.NodeAvailability("active")},
@ -215,17 +217,17 @@ func TestNodeContextWriteJSON(t *testing.T) {
}{ }{
{ {
expected: []map[string]interface{}{ expected: []map[string]interface{}{
{"Availability": "", "Hostname": "foobar_baz", "ID": "nodeID1", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown"}, {"Availability": "", "Hostname": "foobar_baz", "ID": "nodeID1", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown", "EngineVersion": "1.2.3"},
{"Availability": "", "Hostname": "foobar_bar", "ID": "nodeID2", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown"}, {"Availability": "", "Hostname": "foobar_bar", "ID": "nodeID2", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown", "EngineVersion": ""},
{"Availability": "", "Hostname": "foobar_boo", "ID": "nodeID3", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown"}, {"Availability": "", "Hostname": "foobar_boo", "ID": "nodeID3", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown", "EngineVersion": "18.03.0-ce"},
}, },
info: types.Info{}, info: types.Info{},
}, },
{ {
expected: []map[string]interface{}{ expected: []map[string]interface{}{
{"Availability": "", "Hostname": "foobar_baz", "ID": "nodeID1", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Ready"}, {"Availability": "", "Hostname": "foobar_baz", "ID": "nodeID1", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Ready", "EngineVersion": "1.2.3"},
{"Availability": "", "Hostname": "foobar_bar", "ID": "nodeID2", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Needs Rotation"}, {"Availability": "", "Hostname": "foobar_bar", "ID": "nodeID2", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Needs Rotation", "EngineVersion": ""},
{"Availability": "", "Hostname": "foobar_boo", "ID": "nodeID3", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown"}, {"Availability": "", "Hostname": "foobar_boo", "ID": "nodeID3", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown", "EngineVersion": "18.03.0-ce"},
}, },
info: types.Info{ info: types.Info{
Swarm: swarm.Info{ Swarm: swarm.Info{
@ -240,9 +242,9 @@ func TestNodeContextWriteJSON(t *testing.T) {
for _, testcase := range cases { for _, testcase := range cases {
nodes := []swarm.Node{ nodes := []swarm.Node{
{ID: "nodeID1", Description: swarm.NodeDescription{Hostname: "foobar_baz", TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}}, {ID: "nodeID1", Description: swarm.NodeDescription{Hostname: "foobar_baz", TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}, Engine: swarm.EngineDescription{EngineVersion: "1.2.3"}}},
{ID: "nodeID2", Description: swarm.NodeDescription{Hostname: "foobar_bar", TLSInfo: swarm.TLSInfo{TrustRoot: "no"}}}, {ID: "nodeID2", Description: swarm.NodeDescription{Hostname: "foobar_bar", TLSInfo: swarm.TLSInfo{TrustRoot: "no"}}},
{ID: "nodeID3", Description: swarm.NodeDescription{Hostname: "foobar_boo"}}, {ID: "nodeID3", Description: swarm.NodeDescription{Hostname: "foobar_boo", Engine: swarm.EngineDescription{EngineVersion: "18.03.0-ce"}}},
} }
out := bytes.NewBufferString("") out := bytes.NewBufferString("")
err := NodeWrite(Context{Format: "{{json .}}", Output: out}, nodes, testcase.info) err := NodeWrite(Context{Format: "{{json .}}", Output: out}, nodes, testcase.info)

View File

@ -56,8 +56,8 @@ func TestNodeList(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{ cli := test.NewFakeCli(&fakeClient{
nodeListFunc: func() ([]swarm.Node, error) { nodeListFunc: func() ([]swarm.Node, error) {
return []swarm.Node{ return []swarm.Node{
*Node(NodeID("nodeID1"), Hostname("node-2-foo"), Manager(Leader())), *Node(NodeID("nodeID1"), Hostname("node-2-foo"), Manager(Leader()), EngineVersion(".")),
*Node(NodeID("nodeID2"), Hostname("node-10-foo"), Manager()), *Node(NodeID("nodeID2"), Hostname("node-10-foo"), Manager(), EngineVersion("18.03.0-ce")),
*Node(NodeID("nodeID3"), Hostname("node-1-foo")), *Node(NodeID("nodeID3"), Hostname("node-1-foo")),
}, nil }, nil
}, },

View File

@ -1,4 +1,4 @@
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
nodeID3 node-1-foo Ready Active nodeID3 node-1-foo Ready Active 1.13.0
nodeID1 * node-2-foo Ready Active Leader nodeID1 * node-2-foo Ready Active Leader .
nodeID2 node-10-foo Ready Active Reachable nodeID2 node-10-foo Ready Active Reachable 18.03.0-ce

View File

@ -146,6 +146,7 @@ Placeholder | Description
`.Availability` | Node availability ("active", "pause", or "drain") `.Availability` | Node availability ("active", "pause", or "drain")
`.ManagerStatus` | Manager status of the node `.ManagerStatus` | Manager status of the node
`.TLSStatus` | TLS status of the node ("Ready", or "Needs Rotation" has TLS certificate signed by an old CA) `.TLSStatus` | TLS status of the node ("Ready", or "Needs Rotation" has TLS certificate signed by an old CA)
`.EngineVersion` | Engine version
When using the `--format` option, the `node ls` command will either When using the `--format` option, the `node ls` command will either
output the data exactly as the template declares or, when using the output the data exactly as the template declares or, when using the

View File

@ -125,3 +125,10 @@ func ManagerStatus(managerStatusBuilders ...func(*swarm.ManagerStatus)) *swarm.M
return managerStatus return managerStatus
} }
// EngineVersion sets the node's engine version
func EngineVersion(version string) func(*swarm.Node) {
return func(node *swarm.Node) {
node.Description.Engine.EngineVersion = version
}
}