mirror of https://github.com/docker/cli.git
Propagate the swarm cluster and node TLS info provided by the REST API
responses to the CLI. In `node ls`, display only whether the nodes' TLS info matches the cluster's TLS info, or whether the node needs cert rotation. Signed-off-by: Ying Li <ying.li@docker.com>
This commit is contained in:
parent
3574e6a674
commit
b75858eb09
|
@ -1,7 +1,9 @@
|
||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
@ -61,12 +63,20 @@ Engine Labels:
|
||||||
{{- range $k, $v := .EngineLabels }}
|
{{- range $k, $v := .EngineLabels }}
|
||||||
- {{ $k }}{{if $v }}={{ $v }}{{ end }}
|
- {{ $k }}{{if $v }}={{ $v }}{{ end }}
|
||||||
{{- end }}{{- end }}
|
{{- end }}{{- end }}
|
||||||
|
{{- if .HasTLSInfo}}
|
||||||
|
TLS Info:
|
||||||
|
TrustRoot:
|
||||||
|
{{.TLSInfoTrustRoot}}
|
||||||
|
Issuer Subject: {{.TLSInfoCertIssuerSubject}}
|
||||||
|
Issuer Public Key: {{.TLSInfoCertIssuerPublicKey}}
|
||||||
|
{{- end}}
|
||||||
`
|
`
|
||||||
nodeIDHeader = "ID"
|
nodeIDHeader = "ID"
|
||||||
selfHeader = ""
|
selfHeader = ""
|
||||||
hostnameHeader = "HOSTNAME"
|
hostnameHeader = "HOSTNAME"
|
||||||
availabilityHeader = "AVAILABILITY"
|
availabilityHeader = "AVAILABILITY"
|
||||||
managerStatusHeader = "MANAGER STATUS"
|
managerStatusHeader = "MANAGER STATUS"
|
||||||
|
tlsStatusHeader = "TLS STATUS"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewNodeFormat returns a Format for rendering using a node Context
|
// NewNodeFormat returns a Format for rendering using a node Context
|
||||||
|
@ -99,15 +109,17 @@ func NodeWrite(ctx Context, nodes []swarm.Node, info types.Info) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
nodeCtx := nodeContext{}
|
header := nodeHeaderContext{
|
||||||
nodeCtx.header = nodeHeaderContext{
|
|
||||||
"ID": nodeIDHeader,
|
"ID": nodeIDHeader,
|
||||||
"Self": selfHeader,
|
"Self": selfHeader,
|
||||||
"Hostname": hostnameHeader,
|
"Hostname": hostnameHeader,
|
||||||
"Status": statusHeader,
|
"Status": statusHeader,
|
||||||
"Availability": availabilityHeader,
|
"Availability": availabilityHeader,
|
||||||
"ManagerStatus": managerStatusHeader,
|
"ManagerStatus": managerStatusHeader,
|
||||||
|
"TLSStatus": tlsStatusHeader,
|
||||||
}
|
}
|
||||||
|
nodeCtx := nodeContext{}
|
||||||
|
nodeCtx.header = header
|
||||||
return ctx.Write(&nodeCtx, render)
|
return ctx.Write(&nodeCtx, render)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,6 +167,16 @@ func (c *nodeContext) ManagerStatus() string {
|
||||||
return command.PrettyPrint(reachability)
|
return command.PrettyPrint(reachability)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *nodeContext) TLSStatus() string {
|
||||||
|
if c.info.Swarm.Cluster == nil || reflect.DeepEqual(c.info.Swarm.Cluster.TLSInfo, swarm.TLSInfo{}) || reflect.DeepEqual(c.n.Description.TLSInfo, swarm.TLSInfo{}) {
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
if reflect.DeepEqual(c.n.Description.TLSInfo, c.info.Swarm.Cluster.TLSInfo) {
|
||||||
|
return "Ready"
|
||||||
|
}
|
||||||
|
return "Needs Rotation"
|
||||||
|
}
|
||||||
|
|
||||||
// NodeInspectWrite renders the context for a list of services
|
// NodeInspectWrite renders the context for a list of services
|
||||||
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 {
|
||||||
|
@ -290,3 +312,20 @@ func (ctx *nodeInspectContext) EngineLabels() map[string]string {
|
||||||
func (ctx *nodeInspectContext) EngineVersion() string {
|
func (ctx *nodeInspectContext) EngineVersion() string {
|
||||||
return ctx.Node.Description.Engine.EngineVersion
|
return ctx.Node.Description.Engine.EngineVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctx *nodeInspectContext) HasTLSInfo() bool {
|
||||||
|
tlsInfo := ctx.Node.Description.TLSInfo
|
||||||
|
return !reflect.DeepEqual(tlsInfo, swarm.TLSInfo{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *nodeInspectContext) TLSInfoTrustRoot() string {
|
||||||
|
return ctx.Node.Description.TLSInfo.TrustRoot
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *nodeInspectContext) TLSInfoCertIssuerPublicKey() string {
|
||||||
|
return base64.StdEncoding.EncodeToString(ctx.Node.Description.TLSInfo.CertIssuerPublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *nodeInspectContext) TLSInfoCertIssuerSubject() string {
|
||||||
|
return base64.StdEncoding.EncodeToString(ctx.Node.Description.TLSInfo.CertIssuerSubject)
|
||||||
|
}
|
||||||
|
|
|
@ -51,53 +51,81 @@ func TestNodeContext(t *testing.T) {
|
||||||
|
|
||||||
func TestNodeContextWrite(t *testing.T) {
|
func TestNodeContextWrite(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
context Context
|
context Context
|
||||||
expected string
|
expected string
|
||||||
|
clusterInfo swarm.ClusterInfo
|
||||||
}{
|
}{
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
{
|
{
|
||||||
Context{Format: "{{InvalidFunction}}"},
|
context: Context{Format: "{{InvalidFunction}}"},
|
||||||
`Template parsing error: template: :1: function "InvalidFunction" not defined
|
expected: `Template parsing error: template: :1: function "InvalidFunction" not defined
|
||||||
`,
|
`,
|
||||||
|
clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Context{Format: "{{nil}}"},
|
context: Context{Format: "{{nil}}"},
|
||||||
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
|
expected: `Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
|
||||||
`,
|
`,
|
||||||
|
clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
|
||||||
},
|
},
|
||||||
// Table format
|
// Table format
|
||||||
{
|
{
|
||||||
Context{Format: NewNodeFormat("table", false)},
|
context: Context{Format: NewNodeFormat("table", false)},
|
||||||
`ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
|
expected: `ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
|
||||||
nodeID1 foobar_baz Foo Drain Leader
|
nodeID1 foobar_baz Foo Drain Leader
|
||||||
nodeID2 foobar_bar Bar Active Reachable
|
nodeID2 foobar_bar Bar Active Reachable
|
||||||
`,
|
nodeID3 foobar_boo Boo Active ` + "\n", // (to preserve whitespace)
|
||||||
|
clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Context{Format: NewNodeFormat("table", true)},
|
context: Context{Format: NewNodeFormat("table", true)},
|
||||||
`nodeID1
|
expected: `nodeID1
|
||||||
nodeID2
|
nodeID2
|
||||||
|
nodeID3
|
||||||
`,
|
`,
|
||||||
|
clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Context{Format: NewNodeFormat("table {{.Hostname}}", false)},
|
context: Context{Format: NewNodeFormat("table {{.Hostname}}", false)},
|
||||||
`HOSTNAME
|
expected: `HOSTNAME
|
||||||
foobar_baz
|
foobar_baz
|
||||||
foobar_bar
|
foobar_bar
|
||||||
|
foobar_boo
|
||||||
`,
|
`,
|
||||||
|
clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Context{Format: NewNodeFormat("table {{.Hostname}}", true)},
|
context: Context{Format: NewNodeFormat("table {{.Hostname}}", true)},
|
||||||
`HOSTNAME
|
expected: `HOSTNAME
|
||||||
foobar_baz
|
foobar_baz
|
||||||
foobar_bar
|
foobar_bar
|
||||||
|
foobar_boo
|
||||||
`,
|
`,
|
||||||
|
clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
context: Context{Format: NewNodeFormat("table {{.ID}}\t{{.Hostname}}\t{{.TLSStatus}}", false)},
|
||||||
|
expected: `ID HOSTNAME TLS STATUS
|
||||||
|
nodeID1 foobar_baz Needs Rotation
|
||||||
|
nodeID2 foobar_bar Ready
|
||||||
|
nodeID3 foobar_boo Unknown
|
||||||
|
`,
|
||||||
|
clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
|
||||||
|
},
|
||||||
|
{ // no cluster TLS status info, TLS status for all nodes is unknown
|
||||||
|
context: Context{Format: NewNodeFormat("table {{.ID}}\t{{.Hostname}}\t{{.TLSStatus}}", false)},
|
||||||
|
expected: `ID HOSTNAME TLS STATUS
|
||||||
|
nodeID1 foobar_baz Unknown
|
||||||
|
nodeID2 foobar_bar Unknown
|
||||||
|
nodeID3 foobar_boo Unknown
|
||||||
|
`,
|
||||||
|
clusterInfo: swarm.ClusterInfo{},
|
||||||
},
|
},
|
||||||
// Raw Format
|
// Raw Format
|
||||||
{
|
{
|
||||||
Context{Format: NewNodeFormat("raw", false)},
|
context: Context{Format: NewNodeFormat("raw", false)},
|
||||||
`node_id: nodeID1
|
expected: `node_id: nodeID1
|
||||||
hostname: foobar_baz
|
hostname: foobar_baz
|
||||||
status: Foo
|
status: Foo
|
||||||
availability: Drain
|
availability: Drain
|
||||||
|
@ -109,46 +137,67 @@ status: Bar
|
||||||
availability: Active
|
availability: Active
|
||||||
manager_status: Reachable
|
manager_status: Reachable
|
||||||
|
|
||||||
`,
|
node_id: nodeID3
|
||||||
|
hostname: foobar_boo
|
||||||
|
status: Boo
|
||||||
|
availability: Active
|
||||||
|
manager_status: ` + "\n\n", // to preserve whitespace
|
||||||
|
clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Context{Format: NewNodeFormat("raw", true)},
|
context: Context{Format: NewNodeFormat("raw", true)},
|
||||||
`node_id: nodeID1
|
expected: `node_id: nodeID1
|
||||||
node_id: nodeID2
|
node_id: nodeID2
|
||||||
|
node_id: nodeID3
|
||||||
`,
|
`,
|
||||||
|
clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
|
||||||
},
|
},
|
||||||
// Custom Format
|
// Custom Format
|
||||||
{
|
{
|
||||||
Context{Format: NewNodeFormat("{{.Hostname}}", false)},
|
context: Context{Format: NewNodeFormat("{{.Hostname}} {{.TLSStatus}}", false)},
|
||||||
`foobar_baz
|
expected: `foobar_baz Needs Rotation
|
||||||
foobar_bar
|
foobar_bar Ready
|
||||||
|
foobar_boo Unknown
|
||||||
`,
|
`,
|
||||||
|
clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testcase := range cases {
|
for _, testcase := range cases {
|
||||||
nodes := []swarm.Node{
|
nodes := []swarm.Node{
|
||||||
{
|
{
|
||||||
ID: "nodeID1",
|
ID: "nodeID1",
|
||||||
Description: swarm.NodeDescription{Hostname: "foobar_baz"},
|
Description: swarm.NodeDescription{
|
||||||
|
Hostname: "foobar_baz",
|
||||||
|
TLSInfo: swarm.TLSInfo{TrustRoot: "no"},
|
||||||
|
},
|
||||||
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")},
|
||||||
ManagerStatus: &swarm.ManagerStatus{Leader: true},
|
ManagerStatus: &swarm.ManagerStatus{Leader: true},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "nodeID2",
|
ID: "nodeID2",
|
||||||
Description: swarm.NodeDescription{Hostname: "foobar_bar"},
|
Description: swarm.NodeDescription{
|
||||||
Status: swarm.NodeStatus{State: swarm.NodeState("bar")},
|
Hostname: "foobar_bar",
|
||||||
Spec: swarm.NodeSpec{Availability: swarm.NodeAvailability("active")},
|
TLSInfo: swarm.TLSInfo{TrustRoot: "hi"},
|
||||||
|
},
|
||||||
|
Status: swarm.NodeStatus{State: swarm.NodeState("bar")},
|
||||||
|
Spec: swarm.NodeSpec{Availability: swarm.NodeAvailability("active")},
|
||||||
ManagerStatus: &swarm.ManagerStatus{
|
ManagerStatus: &swarm.ManagerStatus{
|
||||||
Leader: false,
|
Leader: false,
|
||||||
Reachability: swarm.Reachability("Reachable"),
|
Reachability: swarm.Reachability("Reachable"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ID: "nodeID3",
|
||||||
|
Description: swarm.NodeDescription{Hostname: "foobar_boo"},
|
||||||
|
Status: swarm.NodeStatus{State: swarm.NodeState("boo")},
|
||||||
|
Spec: swarm.NodeSpec{Availability: swarm.NodeAvailability("active")},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
out := bytes.NewBufferString("")
|
out := bytes.NewBufferString("")
|
||||||
testcase.context.Output = out
|
testcase.context.Output = out
|
||||||
err := NodeWrite(testcase.context, nodes, types.Info{})
|
err := NodeWrite(testcase.context, nodes, types.Info{Swarm: swarm.Info{Cluster: &testcase.clusterInfo}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.EqualError(t, err, testcase.expected)
|
assert.EqualError(t, err, testcase.expected)
|
||||||
} else {
|
} else {
|
||||||
|
@ -158,27 +207,54 @@ foobar_bar
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNodeContextWriteJSON(t *testing.T) {
|
func TestNodeContextWriteJSON(t *testing.T) {
|
||||||
nodes := []swarm.Node{
|
cases := []struct {
|
||||||
{ID: "nodeID1", Description: swarm.NodeDescription{Hostname: "foobar_baz"}},
|
expected []map[string]interface{}
|
||||||
{ID: "nodeID2", Description: swarm.NodeDescription{Hostname: "foobar_bar"}},
|
info types.Info
|
||||||
}
|
}{
|
||||||
expectedJSONs := []map[string]interface{}{
|
{
|
||||||
{"Availability": "", "Hostname": "foobar_baz", "ID": "nodeID1", "ManagerStatus": "", "Status": "", "Self": false},
|
expected: []map[string]interface{}{
|
||||||
{"Availability": "", "Hostname": "foobar_bar", "ID": "nodeID2", "ManagerStatus": "", "Status": "", "Self": false},
|
{"Availability": "", "Hostname": "foobar_baz", "ID": "nodeID1", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown"},
|
||||||
|
{"Availability": "", "Hostname": "foobar_bar", "ID": "nodeID2", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown"},
|
||||||
|
{"Availability": "", "Hostname": "foobar_boo", "ID": "nodeID3", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown"},
|
||||||
|
},
|
||||||
|
info: types.Info{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expected: []map[string]interface{}{
|
||||||
|
{"Availability": "", "Hostname": "foobar_baz", "ID": "nodeID1", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Ready"},
|
||||||
|
{"Availability": "", "Hostname": "foobar_bar", "ID": "nodeID2", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Needs Rotation"},
|
||||||
|
{"Availability": "", "Hostname": "foobar_boo", "ID": "nodeID3", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown"},
|
||||||
|
},
|
||||||
|
info: types.Info{
|
||||||
|
Swarm: swarm.Info{
|
||||||
|
Cluster: &swarm.ClusterInfo{
|
||||||
|
TLSInfo: swarm.TLSInfo{TrustRoot: "hi"},
|
||||||
|
RootRotationInProgress: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
out := bytes.NewBufferString("")
|
for _, testcase := range cases {
|
||||||
err := NodeWrite(Context{Format: "{{json .}}", Output: out}, nodes, types.Info{})
|
nodes := []swarm.Node{
|
||||||
if err != nil {
|
{ID: "nodeID1", Description: swarm.NodeDescription{Hostname: "foobar_baz", TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}},
|
||||||
t.Fatal(err)
|
{ID: "nodeID2", Description: swarm.NodeDescription{Hostname: "foobar_bar", TLSInfo: swarm.TLSInfo{TrustRoot: "no"}}},
|
||||||
}
|
{ID: "nodeID3", Description: swarm.NodeDescription{Hostname: "foobar_boo"}},
|
||||||
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
|
}
|
||||||
t.Logf("Output: line %d: %s", i, line)
|
out := bytes.NewBufferString("")
|
||||||
var m map[string]interface{}
|
err := NodeWrite(Context{Format: "{{json .}}", Output: out}, nodes, testcase.info)
|
||||||
if err := json.Unmarshal([]byte(line), &m); err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
assert.Equal(t, expectedJSONs[i], m)
|
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.Equal(t, testcase.expected[i], m)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,3 +277,71 @@ func TestNodeContextWriteJSONField(t *testing.T) {
|
||||||
assert.Equal(t, nodes[i].ID, s)
|
assert.Equal(t, nodes[i].ID, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNodeInspectWriteContext(t *testing.T) {
|
||||||
|
node := swarm.Node{
|
||||||
|
ID: "nodeID1",
|
||||||
|
Description: swarm.NodeDescription{
|
||||||
|
Hostname: "foobar_baz",
|
||||||
|
TLSInfo: swarm.TLSInfo{
|
||||||
|
TrustRoot: "-----BEGIN CERTIFICATE-----\ndata\n-----END CERTIFICATE-----\n",
|
||||||
|
CertIssuerPublicKey: []byte("pubKey"),
|
||||||
|
CertIssuerSubject: []byte("subject"),
|
||||||
|
},
|
||||||
|
Platform: swarm.Platform{
|
||||||
|
OS: "linux",
|
||||||
|
Architecture: "amd64",
|
||||||
|
},
|
||||||
|
Resources: swarm.Resources{
|
||||||
|
MemoryBytes: 1,
|
||||||
|
},
|
||||||
|
Engine: swarm.EngineDescription{
|
||||||
|
EngineVersion: "0.1.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: swarm.NodeStatus{
|
||||||
|
State: swarm.NodeState("ready"),
|
||||||
|
Addr: "1.1.1.1",
|
||||||
|
},
|
||||||
|
Spec: swarm.NodeSpec{
|
||||||
|
Availability: swarm.NodeAvailability("drain"),
|
||||||
|
Role: swarm.NodeRole("manager"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
out := bytes.NewBufferString("")
|
||||||
|
context := Context{
|
||||||
|
Format: NewNodeFormat("pretty", false),
|
||||||
|
Output: out,
|
||||||
|
}
|
||||||
|
err := NodeInspectWrite(context, []string{"nodeID1"}, func(string) (interface{}, []byte, error) {
|
||||||
|
return node, nil, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected := `ID: nodeID1
|
||||||
|
Hostname: foobar_baz
|
||||||
|
Joined at: 0001-01-01 00:00:00 +0000 utc
|
||||||
|
Status:
|
||||||
|
State: Ready
|
||||||
|
Availability: Drain
|
||||||
|
Address: 1.1.1.1
|
||||||
|
Platform:
|
||||||
|
Operating System: linux
|
||||||
|
Architecture: amd64
|
||||||
|
Resources:
|
||||||
|
CPUs: 0
|
||||||
|
Memory: 1B
|
||||||
|
Engine Version: 0.1.1
|
||||||
|
TLS Info:
|
||||||
|
TrustRoot:
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
data
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
Issuer Subject: c3ViamVjdA==
|
||||||
|
Issuer Public Key: cHViS2V5
|
||||||
|
|
||||||
|
`
|
||||||
|
assert.Equal(t, expected, out.String())
|
||||||
|
}
|
||||||
|
|
|
@ -129,6 +129,7 @@ func prettyPrintInfo(dockerCli *command.DockerCli, info types.Info) error {
|
||||||
fmt.Fprintf(dockerCli.Out(), " %s: %s\n", entry.Protocol, entry.URL)
|
fmt.Fprintf(dockerCli.Out(), " %s: %s\n", entry.Protocol, entry.URL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fmt.Fprintf(dockerCli.Out(), " Root Rotation In Progress: %v\n", info.Swarm.Cluster.RootRotationInProgress)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(dockerCli.Out(), " Node Address: %s\n", info.Swarm.NodeAddr)
|
fmt.Fprintf(dockerCli.Out(), " Node Address: %s\n", info.Swarm.NodeAddr)
|
||||||
managers := []string{}
|
managers := []string{}
|
||||||
|
|
Loading…
Reference in New Issue