github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/service/formatter_test.go (about) 1 // FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16: 2 //go:build go1.19 3 4 package service 5 6 import ( 7 "bytes" 8 "encoding/json" 9 "fmt" 10 "strings" 11 "testing" 12 13 "github.com/docker/cli/cli/command/formatter" 14 "github.com/docker/docker/api/types/swarm" 15 "gotest.tools/v3/assert" 16 is "gotest.tools/v3/assert/cmp" 17 "gotest.tools/v3/golden" 18 ) 19 20 func TestServiceContextWrite(t *testing.T) { 21 var ( 22 // we need a pair of variables for setting the job parameters, because 23 // those parameters take pointers to uint64, which we can't make as a 24 // literal 25 varThree uint64 = 3 26 varTen uint64 = 10 27 ) 28 cases := []struct { 29 context formatter.Context 30 expected string 31 }{ 32 // Errors 33 { 34 formatter.Context{Format: "{{InvalidFunction}}"}, 35 `template parsing error: template: :1: function "InvalidFunction" not defined`, 36 }, 37 { 38 formatter.Context{Format: "{{nil}}"}, 39 `template parsing error: template: :1:2: executing "" at <nil>: nil is not a command`, 40 }, 41 // Table format 42 { 43 formatter.Context{Format: NewListFormat("table", false)}, 44 `ID NAME MODE REPLICAS IMAGE PORTS 45 02_bar bar replicated 2/4 *:80->8090/udp 46 01_baz baz global 1/3 *:80->8080/tcp 47 04_qux2 qux2 replicated 3/3 (max 2 per node) 48 03_qux10 qux10 replicated 2/3 (max 1 per node) 49 05_job1 zarp1 replicated job 2/3 (5/10 completed) 50 06_job2 zarp2 global job 1/1 (3/4 completed) 51 `, 52 }, 53 { 54 formatter.Context{Format: NewListFormat("table", true)}, 55 `02_bar 56 01_baz 57 04_qux2 58 03_qux10 59 05_job1 60 06_job2 61 `, 62 }, 63 { 64 formatter.Context{Format: NewListFormat("table {{.Name}}\t{{.Mode}}", false)}, 65 `NAME MODE 66 bar replicated 67 baz global 68 qux2 replicated 69 qux10 replicated 70 zarp1 replicated job 71 zarp2 global job 72 `, 73 }, 74 { 75 formatter.Context{Format: NewListFormat("table {{.Name}}", true)}, 76 `NAME 77 bar 78 baz 79 qux2 80 qux10 81 zarp1 82 zarp2 83 `, 84 }, 85 // Raw Format 86 { 87 formatter.Context{Format: NewListFormat("raw", false)}, 88 string(golden.Get(t, "service-context-write-raw.golden")), 89 }, 90 { 91 formatter.Context{Format: NewListFormat("raw", true)}, 92 `id: 02_bar 93 id: 01_baz 94 id: 04_qux2 95 id: 03_qux10 96 id: 05_job1 97 id: 06_job2 98 `, 99 }, 100 // Custom Format 101 { 102 formatter.Context{Format: NewListFormat("{{.Name}}", false)}, 103 `bar 104 baz 105 qux2 106 qux10 107 zarp1 108 zarp2 109 `, 110 }, 111 } 112 113 services := []swarm.Service{ 114 { 115 ID: "01_baz", 116 Spec: swarm.ServiceSpec{ 117 Annotations: swarm.Annotations{Name: "baz"}, 118 Mode: swarm.ServiceMode{ 119 Global: &swarm.GlobalService{}, 120 }, 121 }, 122 Endpoint: swarm.Endpoint{ 123 Ports: []swarm.PortConfig{ 124 { 125 PublishMode: "ingress", 126 PublishedPort: 80, 127 TargetPort: 8080, 128 Protocol: "tcp", 129 }, 130 }, 131 }, 132 ServiceStatus: &swarm.ServiceStatus{ 133 RunningTasks: 1, 134 DesiredTasks: 3, 135 }, 136 }, 137 { 138 ID: "02_bar", 139 Spec: swarm.ServiceSpec{ 140 Annotations: swarm.Annotations{Name: "bar"}, 141 Mode: swarm.ServiceMode{ 142 Replicated: &swarm.ReplicatedService{}, 143 }, 144 }, 145 Endpoint: swarm.Endpoint{ 146 Ports: []swarm.PortConfig{ 147 { 148 PublishMode: "ingress", 149 PublishedPort: 80, 150 TargetPort: 8090, 151 Protocol: "udp", 152 }, 153 }, 154 }, 155 ServiceStatus: &swarm.ServiceStatus{ 156 RunningTasks: 2, 157 DesiredTasks: 4, 158 }, 159 }, 160 { 161 ID: "03_qux10", 162 Spec: swarm.ServiceSpec{ 163 Annotations: swarm.Annotations{Name: "qux10"}, 164 Mode: swarm.ServiceMode{ 165 Replicated: &swarm.ReplicatedService{}, 166 }, 167 TaskTemplate: swarm.TaskSpec{ 168 Placement: &swarm.Placement{MaxReplicas: 1}, 169 }, 170 }, 171 ServiceStatus: &swarm.ServiceStatus{ 172 RunningTasks: 2, 173 DesiredTasks: 3, 174 }, 175 }, 176 { 177 ID: "04_qux2", 178 Spec: swarm.ServiceSpec{ 179 Annotations: swarm.Annotations{Name: "qux2"}, 180 Mode: swarm.ServiceMode{ 181 Replicated: &swarm.ReplicatedService{}, 182 }, 183 TaskTemplate: swarm.TaskSpec{ 184 Placement: &swarm.Placement{MaxReplicas: 2}, 185 }, 186 }, 187 ServiceStatus: &swarm.ServiceStatus{ 188 RunningTasks: 3, 189 DesiredTasks: 3, 190 }, 191 }, 192 { 193 ID: "05_job1", 194 Spec: swarm.ServiceSpec{ 195 Annotations: swarm.Annotations{Name: "zarp1"}, 196 Mode: swarm.ServiceMode{ 197 ReplicatedJob: &swarm.ReplicatedJob{ 198 MaxConcurrent: &varThree, 199 TotalCompletions: &varTen, 200 }, 201 }, 202 }, 203 ServiceStatus: &swarm.ServiceStatus{ 204 RunningTasks: 2, 205 DesiredTasks: 3, 206 CompletedTasks: 5, 207 }, 208 }, 209 { 210 ID: "06_job2", 211 Spec: swarm.ServiceSpec{ 212 Annotations: swarm.Annotations{Name: "zarp2"}, 213 Mode: swarm.ServiceMode{ 214 GlobalJob: &swarm.GlobalJob{}, 215 }, 216 }, 217 ServiceStatus: &swarm.ServiceStatus{ 218 RunningTasks: 1, 219 DesiredTasks: 1, 220 CompletedTasks: 3, 221 }, 222 }, 223 } 224 225 for _, tc := range cases { 226 tc := tc 227 t.Run(string(tc.context.Format), func(t *testing.T) { 228 var out bytes.Buffer 229 tc.context.Output = &out 230 231 if err := ListFormatWrite(tc.context, services); err != nil { 232 assert.Error(t, err, tc.expected) 233 } else { 234 assert.Equal(t, out.String(), tc.expected) 235 } 236 }) 237 } 238 } 239 240 func TestServiceContextWriteJSON(t *testing.T) { 241 services := []swarm.Service{ 242 { 243 ID: "01_baz", 244 Spec: swarm.ServiceSpec{ 245 Annotations: swarm.Annotations{Name: "baz"}, 246 Mode: swarm.ServiceMode{ 247 Global: &swarm.GlobalService{}, 248 }, 249 }, 250 Endpoint: swarm.Endpoint{ 251 Ports: []swarm.PortConfig{ 252 { 253 PublishMode: "ingress", 254 PublishedPort: 80, 255 TargetPort: 8080, 256 Protocol: "tcp", 257 }, 258 }, 259 }, 260 ServiceStatus: &swarm.ServiceStatus{ 261 RunningTasks: 1, 262 DesiredTasks: 3, 263 }, 264 }, 265 { 266 ID: "02_bar", 267 Spec: swarm.ServiceSpec{ 268 Annotations: swarm.Annotations{Name: "bar"}, 269 Mode: swarm.ServiceMode{ 270 Replicated: &swarm.ReplicatedService{}, 271 }, 272 }, 273 Endpoint: swarm.Endpoint{ 274 Ports: []swarm.PortConfig{ 275 { 276 PublishMode: "ingress", 277 PublishedPort: 80, 278 TargetPort: 8080, 279 Protocol: "tcp", 280 }, 281 }, 282 }, 283 ServiceStatus: &swarm.ServiceStatus{ 284 RunningTasks: 2, 285 DesiredTasks: 4, 286 }, 287 }, 288 } 289 expectedJSONs := []map[string]any{ 290 {"ID": "02_bar", "Name": "bar", "Mode": "replicated", "Replicas": "2/4", "Image": "", "Ports": "*:80->8080/tcp"}, 291 {"ID": "01_baz", "Name": "baz", "Mode": "global", "Replicas": "1/3", "Image": "", "Ports": "*:80->8080/tcp"}, 292 } 293 294 out := bytes.NewBufferString("") 295 err := ListFormatWrite(formatter.Context{Format: "{{json .}}", Output: out}, services) 296 if err != nil { 297 t.Fatal(err) 298 } 299 for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") { 300 msg := fmt.Sprintf("Output: line %d: %s", i, line) 301 var m map[string]any 302 err := json.Unmarshal([]byte(line), &m) 303 assert.NilError(t, err, msg) 304 assert.Check(t, is.DeepEqual(expectedJSONs[i], m), msg) 305 } 306 } 307 308 func TestServiceContextWriteJSONField(t *testing.T) { 309 services := []swarm.Service{ 310 { 311 ID: "01_baz", 312 Spec: swarm.ServiceSpec{ 313 Annotations: swarm.Annotations{Name: "baz"}, 314 Mode: swarm.ServiceMode{ 315 Global: &swarm.GlobalService{}, 316 }, 317 }, 318 ServiceStatus: &swarm.ServiceStatus{ 319 RunningTasks: 2, 320 DesiredTasks: 4, 321 }, 322 }, 323 { 324 ID: "24_bar", 325 Spec: swarm.ServiceSpec{ 326 Annotations: swarm.Annotations{Name: "bar"}, 327 Mode: swarm.ServiceMode{ 328 Replicated: &swarm.ReplicatedService{}, 329 }, 330 }, 331 ServiceStatus: &swarm.ServiceStatus{ 332 RunningTasks: 2, 333 DesiredTasks: 4, 334 }, 335 }, 336 } 337 out := bytes.NewBufferString("") 338 err := ListFormatWrite(formatter.Context{Format: "{{json .Name}}", Output: out}, services) 339 if err != nil { 340 t.Fatal(err) 341 } 342 for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") { 343 msg := fmt.Sprintf("Output: line %d: %s", i, line) 344 var s string 345 err := json.Unmarshal([]byte(line), &s) 346 assert.NilError(t, err, msg) 347 assert.Check(t, is.Equal(services[i].Spec.Name, s), msg) 348 } 349 } 350 351 func TestServiceContext_Ports(t *testing.T) { 352 c := serviceContext{ 353 service: swarm.Service{ 354 Endpoint: swarm.Endpoint{ 355 Ports: []swarm.PortConfig{ 356 { 357 Protocol: "tcp", 358 TargetPort: 80, 359 PublishedPort: 81, 360 PublishMode: "ingress", 361 }, 362 { 363 Protocol: "tcp", 364 TargetPort: 80, 365 PublishedPort: 80, 366 PublishMode: "ingress", 367 }, 368 { 369 Protocol: "tcp", 370 TargetPort: 95, 371 PublishedPort: 95, 372 PublishMode: "ingress", 373 }, 374 { 375 Protocol: "tcp", 376 TargetPort: 90, 377 PublishedPort: 90, 378 PublishMode: "ingress", 379 }, 380 { 381 Protocol: "tcp", 382 TargetPort: 91, 383 PublishedPort: 91, 384 PublishMode: "ingress", 385 }, 386 { 387 Protocol: "tcp", 388 TargetPort: 92, 389 PublishedPort: 92, 390 PublishMode: "ingress", 391 }, 392 { 393 Protocol: "tcp", 394 TargetPort: 93, 395 PublishedPort: 93, 396 PublishMode: "ingress", 397 }, 398 { 399 Protocol: "tcp", 400 TargetPort: 94, 401 PublishedPort: 94, 402 PublishMode: "ingress", 403 }, 404 { 405 Protocol: "udp", 406 TargetPort: 95, 407 PublishedPort: 95, 408 PublishMode: "ingress", 409 }, 410 { 411 Protocol: "udp", 412 TargetPort: 90, 413 PublishedPort: 90, 414 PublishMode: "ingress", 415 }, 416 { 417 Protocol: "udp", 418 TargetPort: 96, 419 PublishedPort: 96, 420 PublishMode: "ingress", 421 }, 422 { 423 Protocol: "udp", 424 TargetPort: 91, 425 PublishedPort: 91, 426 PublishMode: "ingress", 427 }, 428 { 429 Protocol: "udp", 430 TargetPort: 92, 431 PublishedPort: 92, 432 PublishMode: "ingress", 433 }, 434 { 435 Protocol: "udp", 436 TargetPort: 93, 437 PublishedPort: 93, 438 PublishMode: "ingress", 439 }, 440 { 441 Protocol: "udp", 442 TargetPort: 94, 443 PublishedPort: 94, 444 PublishMode: "ingress", 445 }, 446 { 447 Protocol: "tcp", 448 TargetPort: 60, 449 PublishedPort: 60, 450 PublishMode: "ingress", 451 }, 452 { 453 Protocol: "tcp", 454 TargetPort: 61, 455 PublishedPort: 61, 456 PublishMode: "ingress", 457 }, 458 { 459 Protocol: "tcp", 460 TargetPort: 61, 461 PublishedPort: 62, 462 PublishMode: "ingress", 463 }, 464 { 465 Protocol: "sctp", 466 TargetPort: 97, 467 PublishedPort: 97, 468 PublishMode: "ingress", 469 }, 470 { 471 Protocol: "sctp", 472 TargetPort: 98, 473 PublishedPort: 98, 474 PublishMode: "ingress", 475 }, 476 }, 477 }, 478 }, 479 } 480 481 assert.Check(t, is.Equal("*:97-98->97-98/sctp, *:60-61->60-61/tcp, *:62->61/tcp, *:80-81->80/tcp, *:90-95->90-95/tcp, *:90-96->90-96/udp", c.Ports())) 482 }