github.com/xeptore/docker-cli@v20.10.14+incompatible/cli/command/node/formatter_test.go (about) 1 package node 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "strings" 8 "testing" 9 10 "github.com/docker/cli/cli/command/formatter" 11 "github.com/docker/cli/internal/test" 12 "github.com/docker/docker/api/types" 13 "github.com/docker/docker/api/types/swarm" 14 "github.com/docker/docker/pkg/stringid" 15 "gotest.tools/v3/assert" 16 is "gotest.tools/v3/assert/cmp" 17 ) 18 19 func TestNodeContext(t *testing.T) { 20 nodeID := stringid.GenerateRandomID() 21 22 var ctx nodeContext 23 cases := []struct { 24 nodeCtx nodeContext 25 expValue string 26 call func() string 27 }{ 28 {nodeContext{ 29 n: swarm.Node{ID: nodeID}, 30 }, nodeID, ctx.ID}, 31 {nodeContext{ 32 n: swarm.Node{Description: swarm.NodeDescription{Hostname: "node_hostname"}}, 33 }, "node_hostname", ctx.Hostname}, 34 {nodeContext{ 35 n: swarm.Node{Status: swarm.NodeStatus{State: swarm.NodeState("foo")}}, 36 }, "Foo", ctx.Status}, 37 {nodeContext{ 38 n: swarm.Node{Spec: swarm.NodeSpec{Availability: swarm.NodeAvailability("drain")}}, 39 }, "Drain", ctx.Availability}, 40 {nodeContext{ 41 n: swarm.Node{ManagerStatus: &swarm.ManagerStatus{Leader: true}}, 42 }, "Leader", ctx.ManagerStatus}, 43 } 44 45 for _, c := range cases { 46 ctx = c.nodeCtx 47 v := c.call() 48 if strings.Contains(v, ",") { 49 test.CompareMultipleValues(t, v, c.expValue) 50 } else if v != c.expValue { 51 t.Fatalf("Expected %s, was %s\n", c.expValue, v) 52 } 53 } 54 } 55 56 func TestNodeContextWrite(t *testing.T) { 57 cases := []struct { 58 context formatter.Context 59 expected string 60 clusterInfo swarm.ClusterInfo 61 }{ 62 63 // Errors 64 { 65 context: formatter.Context{Format: "{{InvalidFunction}}"}, 66 expected: `Template parsing error: template: :1: function "InvalidFunction" not defined 67 `, 68 clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, 69 }, 70 { 71 context: formatter.Context{Format: "{{nil}}"}, 72 expected: `Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command 73 `, 74 clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, 75 }, 76 // Table format 77 { 78 context: formatter.Context{Format: NewFormat("table", false)}, 79 expected: `ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 80 nodeID1 foobar_baz Foo Drain Leader 18.03.0-ce 81 nodeID2 foobar_bar Bar Active Reachable 1.2.3 82 nodeID3 foobar_boo Boo Active ` + "\n", // (to preserve whitespace) 83 clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, 84 }, 85 { 86 context: formatter.Context{Format: NewFormat("table", true)}, 87 expected: `nodeID1 88 nodeID2 89 nodeID3 90 `, 91 clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, 92 }, 93 { 94 context: formatter.Context{Format: NewFormat("table {{.Hostname}}", false)}, 95 expected: `HOSTNAME 96 foobar_baz 97 foobar_bar 98 foobar_boo 99 `, 100 clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, 101 }, 102 { 103 context: formatter.Context{Format: NewFormat("table {{.Hostname}}", true)}, 104 expected: `HOSTNAME 105 foobar_baz 106 foobar_bar 107 foobar_boo 108 `, 109 clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, 110 }, 111 { 112 context: formatter.Context{Format: NewFormat("table {{.ID}}\t{{.Hostname}}\t{{.TLSStatus}}", false)}, 113 expected: `ID HOSTNAME TLS STATUS 114 nodeID1 foobar_baz Needs Rotation 115 nodeID2 foobar_bar Ready 116 nodeID3 foobar_boo Unknown 117 `, 118 clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, 119 }, 120 { // no cluster TLS status info, TLS status for all nodes is unknown 121 context: formatter.Context{Format: NewFormat("table {{.ID}}\t{{.Hostname}}\t{{.TLSStatus}}", false)}, 122 expected: `ID HOSTNAME TLS STATUS 123 nodeID1 foobar_baz Unknown 124 nodeID2 foobar_bar Unknown 125 nodeID3 foobar_boo Unknown 126 `, 127 clusterInfo: swarm.ClusterInfo{}, 128 }, 129 // Raw Format 130 { 131 context: formatter.Context{Format: NewFormat("raw", false)}, 132 expected: `node_id: nodeID1 133 hostname: foobar_baz 134 status: Foo 135 availability: Drain 136 manager_status: Leader 137 138 node_id: nodeID2 139 hostname: foobar_bar 140 status: Bar 141 availability: Active 142 manager_status: Reachable 143 144 node_id: nodeID3 145 hostname: foobar_boo 146 status: Boo 147 availability: Active 148 manager_status: ` + "\n\n", // to preserve whitespace 149 clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, 150 }, 151 { 152 context: formatter.Context{Format: NewFormat("raw", true)}, 153 expected: `node_id: nodeID1 154 node_id: nodeID2 155 node_id: nodeID3 156 `, 157 clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, 158 }, 159 // Custom Format 160 { 161 context: formatter.Context{Format: NewFormat("{{.Hostname}} {{.TLSStatus}}", false)}, 162 expected: `foobar_baz Needs Rotation 163 foobar_bar Ready 164 foobar_boo Unknown 165 `, 166 clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, 167 }, 168 } 169 170 nodes := []swarm.Node{ 171 { 172 ID: "nodeID1", 173 Description: swarm.NodeDescription{ 174 Hostname: "foobar_baz", 175 TLSInfo: swarm.TLSInfo{TrustRoot: "no"}, 176 Engine: swarm.EngineDescription{EngineVersion: "18.03.0-ce"}, 177 }, 178 Status: swarm.NodeStatus{State: swarm.NodeState("foo")}, 179 Spec: swarm.NodeSpec{Availability: swarm.NodeAvailability("drain")}, 180 ManagerStatus: &swarm.ManagerStatus{Leader: true}, 181 }, 182 { 183 ID: "nodeID2", 184 Description: swarm.NodeDescription{ 185 Hostname: "foobar_bar", 186 TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}, 187 Engine: swarm.EngineDescription{EngineVersion: "1.2.3"}, 188 }, 189 Status: swarm.NodeStatus{State: swarm.NodeState("bar")}, 190 Spec: swarm.NodeSpec{Availability: swarm.NodeAvailability("active")}, 191 ManagerStatus: &swarm.ManagerStatus{ 192 Leader: false, 193 Reachability: swarm.Reachability("Reachable"), 194 }, 195 }, 196 { 197 ID: "nodeID3", 198 Description: swarm.NodeDescription{Hostname: "foobar_boo"}, 199 Status: swarm.NodeStatus{State: swarm.NodeState("boo")}, 200 Spec: swarm.NodeSpec{Availability: swarm.NodeAvailability("active")}, 201 }, 202 } 203 204 for _, tc := range cases { 205 tc := tc 206 t.Run(string(tc.context.Format), func(t *testing.T) { 207 var out bytes.Buffer 208 tc.context.Output = &out 209 210 err := FormatWrite(tc.context, nodes, types.Info{Swarm: swarm.Info{Cluster: &tc.clusterInfo}}) 211 if err != nil { 212 assert.Error(t, err, tc.expected) 213 } else { 214 assert.Equal(t, out.String(), tc.expected) 215 } 216 }) 217 } 218 } 219 220 func TestNodeContextWriteJSON(t *testing.T) { 221 cases := []struct { 222 expected []map[string]interface{} 223 info types.Info 224 }{ 225 { 226 expected: []map[string]interface{}{ 227 {"Availability": "", "Hostname": "foobar_baz", "ID": "nodeID1", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown", "EngineVersion": "1.2.3"}, 228 {"Availability": "", "Hostname": "foobar_bar", "ID": "nodeID2", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown", "EngineVersion": ""}, 229 {"Availability": "", "Hostname": "foobar_boo", "ID": "nodeID3", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown", "EngineVersion": "18.03.0-ce"}, 230 }, 231 info: types.Info{}, 232 }, 233 { 234 expected: []map[string]interface{}{ 235 {"Availability": "", "Hostname": "foobar_baz", "ID": "nodeID1", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Ready", "EngineVersion": "1.2.3"}, 236 {"Availability": "", "Hostname": "foobar_bar", "ID": "nodeID2", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Needs Rotation", "EngineVersion": ""}, 237 {"Availability": "", "Hostname": "foobar_boo", "ID": "nodeID3", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown", "EngineVersion": "18.03.0-ce"}, 238 }, 239 info: types.Info{ 240 Swarm: swarm.Info{ 241 Cluster: &swarm.ClusterInfo{ 242 TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}, 243 RootRotationInProgress: true, 244 }, 245 }, 246 }, 247 }, 248 } 249 250 for _, testcase := range cases { 251 nodes := []swarm.Node{ 252 {ID: "nodeID1", Description: swarm.NodeDescription{Hostname: "foobar_baz", TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}, Engine: swarm.EngineDescription{EngineVersion: "1.2.3"}}}, 253 {ID: "nodeID2", Description: swarm.NodeDescription{Hostname: "foobar_bar", TLSInfo: swarm.TLSInfo{TrustRoot: "no"}}}, 254 {ID: "nodeID3", Description: swarm.NodeDescription{Hostname: "foobar_boo", Engine: swarm.EngineDescription{EngineVersion: "18.03.0-ce"}}}, 255 } 256 out := bytes.NewBufferString("") 257 err := FormatWrite(formatter.Context{Format: "{{json .}}", Output: out}, nodes, testcase.info) 258 if err != nil { 259 t.Fatal(err) 260 } 261 for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") { 262 msg := fmt.Sprintf("Output: line %d: %s", i, line) 263 var m map[string]interface{} 264 err := json.Unmarshal([]byte(line), &m) 265 assert.NilError(t, err, msg) 266 assert.Check(t, is.DeepEqual(testcase.expected[i], m), msg) 267 } 268 } 269 } 270 271 func TestNodeContextWriteJSONField(t *testing.T) { 272 nodes := []swarm.Node{ 273 {ID: "nodeID1", Description: swarm.NodeDescription{Hostname: "foobar_baz"}}, 274 {ID: "nodeID2", Description: swarm.NodeDescription{Hostname: "foobar_bar"}}, 275 } 276 out := bytes.NewBufferString("") 277 err := FormatWrite(formatter.Context{Format: "{{json .ID}}", Output: out}, nodes, types.Info{}) 278 if err != nil { 279 t.Fatal(err) 280 } 281 for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") { 282 msg := fmt.Sprintf("Output: line %d: %s", i, line) 283 var s string 284 err := json.Unmarshal([]byte(line), &s) 285 assert.NilError(t, err, msg) 286 assert.Check(t, is.Equal(nodes[i].ID, s), msg) 287 } 288 } 289 290 func TestNodeInspectWriteContext(t *testing.T) { 291 node := swarm.Node{ 292 ID: "nodeID1", 293 Description: swarm.NodeDescription{ 294 Hostname: "foobar_baz", 295 TLSInfo: swarm.TLSInfo{ 296 TrustRoot: "-----BEGIN CERTIFICATE-----\ndata\n-----END CERTIFICATE-----\n", 297 CertIssuerPublicKey: []byte("pubKey"), 298 CertIssuerSubject: []byte("subject"), 299 }, 300 Platform: swarm.Platform{ 301 OS: "linux", 302 Architecture: "amd64", 303 }, 304 Resources: swarm.Resources{ 305 MemoryBytes: 1, 306 }, 307 Engine: swarm.EngineDescription{ 308 EngineVersion: "0.1.1", 309 }, 310 }, 311 Status: swarm.NodeStatus{ 312 State: swarm.NodeState("ready"), 313 Addr: "1.1.1.1", 314 }, 315 Spec: swarm.NodeSpec{ 316 Availability: swarm.NodeAvailability("drain"), 317 Role: swarm.NodeRole("manager"), 318 }, 319 } 320 out := bytes.NewBufferString("") 321 context := formatter.Context{ 322 Format: NewFormat("pretty", false), 323 Output: out, 324 } 325 err := InspectFormatWrite(context, []string{"nodeID1"}, func(string) (interface{}, []byte, error) { 326 return node, nil, nil 327 }) 328 if err != nil { 329 t.Fatal(err) 330 } 331 expected := `ID: nodeID1 332 Hostname: foobar_baz 333 Joined at: 0001-01-01 00:00:00 +0000 utc 334 Status: 335 State: Ready 336 Availability: Drain 337 Address: 1.1.1.1 338 Platform: 339 Operating System: linux 340 Architecture: amd64 341 Resources: 342 CPUs: 0 343 Memory: 1B 344 Engine Version: 0.1.1 345 TLS Info: 346 TrustRoot: 347 -----BEGIN CERTIFICATE----- 348 data 349 -----END CERTIFICATE----- 350 351 Issuer Subject: c3ViamVjdA== 352 Issuer Public Key: cHViS2V5 353 ` 354 assert.Check(t, is.Equal(expected, out.String())) 355 }