github.com/khulnasoft/cli@v0.0.0-20240402070845-01bcad7beefa/cli/command/system/info_test.go (about) 1 package system 2 3 import ( 4 "encoding/base64" 5 "net" 6 "testing" 7 "time" 8 9 registrytypes "github.com/docker/docker/api/types/registry" 10 "github.com/docker/docker/api/types/swarm" 11 "github.com/docker/docker/api/types/system" 12 pluginmanager "github.com/khulnasoft/cli/cli-plugins/manager" 13 "github.com/khulnasoft/cli/internal/test" 14 "gotest.tools/v3/assert" 15 is "gotest.tools/v3/assert/cmp" 16 "gotest.tools/v3/golden" 17 ) 18 19 // helper function that base64 decodes a string and ignores the error 20 func base64Decode(val string) []byte { 21 decoded, _ := base64.StdEncoding.DecodeString(val) 22 return decoded 23 } 24 25 const sampleID = "EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX" 26 27 var sampleInfoNoSwarm = system.Info{ 28 ID: sampleID, 29 Containers: 0, 30 ContainersRunning: 0, 31 ContainersPaused: 0, 32 ContainersStopped: 0, 33 Images: 0, 34 Driver: "overlay2", 35 DriverStatus: [][2]string{ 36 {"Backing Filesystem", "extfs"}, 37 {"Supports d_type", "true"}, 38 {"Using metacopy", "false"}, 39 {"Native Overlay Diff", "true"}, 40 }, 41 SystemStatus: nil, 42 Plugins: system.PluginsInfo{ 43 Volume: []string{"local"}, 44 Network: []string{"bridge", "host", "macvlan", "null", "overlay"}, 45 Authorization: nil, 46 Log: []string{"awslogs", "fluentd", "gcplogs", "gelf", "journald", "json-file", "splunk", "syslog"}, 47 }, 48 MemoryLimit: true, 49 SwapLimit: true, 50 KernelMemory: true, 51 CPUCfsPeriod: true, 52 CPUCfsQuota: true, 53 CPUShares: true, 54 CPUSet: true, 55 IPv4Forwarding: true, 56 BridgeNfIptables: true, 57 BridgeNfIP6tables: true, 58 Debug: true, 59 NFd: 33, 60 OomKillDisable: true, 61 NGoroutines: 135, 62 SystemTime: "2017-08-24T17:44:34.077811894Z", 63 LoggingDriver: "json-file", 64 CgroupDriver: "cgroupfs", 65 NEventsListener: 0, 66 KernelVersion: "4.4.0-87-generic", 67 OperatingSystem: "Ubuntu 16.04.3 LTS", 68 OSVersion: "", 69 OSType: "linux", 70 Architecture: "x86_64", 71 IndexServerAddress: "https://index.docker.io/v1/", 72 RegistryConfig: ®istrytypes.ServiceConfig{ 73 AllowNondistributableArtifactsCIDRs: nil, 74 AllowNondistributableArtifactsHostnames: nil, 75 InsecureRegistryCIDRs: []*registrytypes.NetIPNet{ 76 { 77 IP: net.ParseIP("127.0.0.0"), 78 Mask: net.IPv4Mask(255, 0, 0, 0), 79 }, 80 }, 81 IndexConfigs: map[string]*registrytypes.IndexInfo{ 82 "docker.io": { 83 Name: "docker.io", 84 Mirrors: nil, 85 Secure: true, 86 Official: true, 87 }, 88 }, 89 Mirrors: nil, 90 }, 91 NCPU: 2, 92 MemTotal: 2097356800, 93 DockerRootDir: "/var/lib/docker", 94 HTTPProxy: "", 95 HTTPSProxy: "", 96 NoProxy: "", 97 Name: "system-sample", 98 Labels: []string{"provider=digitalocean"}, 99 ExperimentalBuild: false, 100 ServerVersion: "17.06.1-ce", 101 Runtimes: map[string]system.RuntimeWithStatus{ 102 "runc": { 103 Runtime: system.Runtime{ 104 Path: "docker-runc", 105 Args: nil, 106 }, 107 }, 108 }, 109 DefaultRuntime: "runc", 110 Swarm: swarm.Info{LocalNodeState: "inactive"}, 111 LiveRestoreEnabled: false, 112 Isolation: "", 113 InitBinary: "docker-init", 114 ContainerdCommit: system.Commit{ 115 ID: "6e23458c129b551d5c9871e5174f6b1b7f6d1170", 116 Expected: "6e23458c129b551d5c9871e5174f6b1b7f6d1170", 117 }, 118 RuncCommit: system.Commit{ 119 ID: "810190ceaa507aa2727d7ae6f4790c76ec150bd2", 120 Expected: "810190ceaa507aa2727d7ae6f4790c76ec150bd2", 121 }, 122 InitCommit: system.Commit{ 123 ID: "949e6fa", 124 Expected: "949e6fa", 125 }, 126 SecurityOptions: []string{"name=apparmor", "name=seccomp,profile=default"}, 127 DefaultAddressPools: []system.NetworkAddressPool{ 128 { 129 Base: "10.123.0.0/16", 130 Size: 24, 131 }, 132 }, 133 CDISpecDirs: []string{"/etc/cdi", "/var/run/cdi"}, 134 } 135 136 var sampleSwarmInfo = swarm.Info{ 137 NodeID: "qo2dfdig9mmxqkawulggepdih", 138 NodeAddr: "165.227.107.89", 139 LocalNodeState: "active", 140 ControlAvailable: true, 141 Error: "", 142 RemoteManagers: []swarm.Peer{ 143 { 144 NodeID: "qo2dfdig9mmxqkawulggepdih", 145 Addr: "165.227.107.89:2377", 146 }, 147 }, 148 Nodes: 1, 149 Managers: 1, 150 Cluster: &swarm.ClusterInfo{ 151 ID: "9vs5ygs0gguyyec4iqf2314c0", 152 Meta: swarm.Meta{ 153 Version: swarm.Version{Index: 11}, 154 CreatedAt: time.Date(2017, 8, 24, 17, 34, 19, 278062352, time.UTC), 155 UpdatedAt: time.Date(2017, 8, 24, 17, 34, 42, 398815481, time.UTC), 156 }, 157 Spec: swarm.Spec{ 158 Annotations: swarm.Annotations{ 159 Name: "default", 160 Labels: nil, 161 }, 162 Orchestration: swarm.OrchestrationConfig{ 163 TaskHistoryRetentionLimit: &[]int64{5}[0], 164 }, 165 Raft: swarm.RaftConfig{ 166 SnapshotInterval: 10000, 167 KeepOldSnapshots: &[]uint64{0}[0], 168 LogEntriesForSlowFollowers: 500, 169 ElectionTick: 3, 170 HeartbeatTick: 1, 171 }, 172 Dispatcher: swarm.DispatcherConfig{ 173 HeartbeatPeriod: 5000000000, 174 }, 175 CAConfig: swarm.CAConfig{ 176 NodeCertExpiry: 7776000000000000, 177 }, 178 TaskDefaults: swarm.TaskDefaults{}, 179 EncryptionConfig: swarm.EncryptionConfig{ 180 AutoLockManagers: true, 181 }, 182 }, 183 TLSInfo: swarm.TLSInfo{ 184 TrustRoot: ` 185 -----BEGIN CERTIFICATE----- 186 MIIBajCCARCgAwIBAgIUaFCW5xsq8eyiJ+Pmcv3MCflMLnMwCgYIKoZIzj0EAwIw 187 EzERMA8GA1UEAxMIc3dhcm0tY2EwHhcNMTcwODI0MTcyOTAwWhcNMzcwODE5MTcy 188 OTAwWjATMREwDwYDVQQDEwhzd2FybS1jYTBZMBMGByqGSM49AgEGCCqGSM49AwEH 189 A0IABDy7NebyUJyUjWJDBUdnZoV6GBxEGKO4TZPNDwnxDxJcUdLVaB7WGa4/DLrW 190 UfsVgh1JGik2VTiLuTMA1tLlNPOjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB 191 Af8EBTADAQH/MB0GA1UdDgQWBBQl16XFtaaXiUAwEuJptJlDjfKskDAKBggqhkjO 192 PQQDAgNIADBFAiEAo9fTQNM5DP9bHVcTJYfl2Cay1bFu1E+lnpmN+EYJfeACIGKH 193 1pCUkZ+D0IB6CiEZGWSHyLuXPM1rlP+I5KuS7sB8 194 -----END CERTIFICATE----- 195 `, 196 CertIssuerSubject: base64Decode("MBMxETAPBgNVBAMTCHN3YXJtLWNh"), 197 CertIssuerPublicKey: base64Decode( 198 "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPLs15vJQnJSNYkMFR2dmhXoYHEQYo7hNk80PCfEPElxR0tVoHtYZrj8MutZR+xWCHUkaKTZVOIu5MwDW0uU08w=="), 199 }, 200 RootRotationInProgress: false, 201 }, 202 } 203 204 var samplePluginsInfo = []pluginmanager.Plugin{ 205 { 206 Name: "goodplugin", 207 Path: "/path/to/docker-goodplugin", 208 Metadata: pluginmanager.Metadata{ 209 SchemaVersion: "0.1.0", 210 ShortDescription: "unit test is good", 211 Vendor: "ACME Corp", 212 Version: "0.1.0", 213 }, 214 }, 215 { 216 Name: "unversionedplugin", 217 Path: "/path/to/docker-unversionedplugin", 218 Metadata: pluginmanager.Metadata{ 219 SchemaVersion: "0.1.0", 220 ShortDescription: "this plugin has no version", 221 Vendor: "ACME Corp", 222 }, 223 }, 224 { 225 Name: "badplugin", 226 Path: "/path/to/docker-badplugin", 227 Err: pluginmanager.NewPluginError("something wrong"), 228 }, 229 } 230 231 func TestPrettyPrintInfo(t *testing.T) { 232 infoWithSwarm := sampleInfoNoSwarm 233 infoWithSwarm.Swarm = sampleSwarmInfo 234 235 infoWithWarningsLinux := sampleInfoNoSwarm 236 infoWithWarningsLinux.MemoryLimit = false 237 infoWithWarningsLinux.SwapLimit = false 238 infoWithWarningsLinux.KernelMemory = false 239 infoWithWarningsLinux.OomKillDisable = false 240 infoWithWarningsLinux.CPUCfsQuota = false 241 infoWithWarningsLinux.CPUCfsPeriod = false 242 infoWithWarningsLinux.CPUShares = false 243 infoWithWarningsLinux.CPUSet = false 244 infoWithWarningsLinux.IPv4Forwarding = false 245 infoWithWarningsLinux.BridgeNfIptables = false 246 infoWithWarningsLinux.BridgeNfIP6tables = false 247 248 sampleInfoDaemonWarnings := sampleInfoNoSwarm 249 sampleInfoDaemonWarnings.Warnings = []string{ 250 "WARNING: No memory limit support", 251 "WARNING: No swap limit support", 252 "WARNING: No oom kill disable support", 253 "WARNING: No cpu cfs quota support", 254 "WARNING: No cpu cfs period support", 255 "WARNING: No cpu shares support", 256 "WARNING: No cpuset support", 257 "WARNING: IPv4 forwarding is disabled", 258 "WARNING: bridge-nf-call-iptables is disabled", 259 "WARNING: bridge-nf-call-ip6tables is disabled", 260 } 261 262 sampleInfoBadSecurity := sampleInfoNoSwarm 263 sampleInfoBadSecurity.SecurityOptions = []string{"foo="} 264 265 sampleInfoLabelsNil := sampleInfoNoSwarm 266 sampleInfoLabelsNil.Labels = nil 267 sampleInfoLabelsEmpty := sampleInfoNoSwarm 268 sampleInfoLabelsEmpty.Labels = []string{} 269 270 for _, tc := range []struct { 271 doc string 272 dockerInfo dockerInfo 273 274 prettyGolden string 275 warningsGolden string 276 jsonGolden string 277 expectedError string 278 }{ 279 { 280 doc: "info without swarm", 281 dockerInfo: dockerInfo{ 282 Info: &sampleInfoNoSwarm, 283 ClientInfo: &clientInfo{ 284 clientVersion: clientVersion{ 285 Platform: &platformInfo{Name: "Docker Engine - Community"}, 286 Version: "24.0.0", 287 Context: "default", 288 }, 289 Debug: true, 290 }, 291 }, 292 prettyGolden: "docker-info-no-swarm", 293 jsonGolden: "docker-info-no-swarm", 294 }, 295 { 296 doc: "info with plugins", 297 dockerInfo: dockerInfo{ 298 Info: &sampleInfoNoSwarm, 299 ClientInfo: &clientInfo{ 300 clientVersion: clientVersion{Context: "default"}, 301 Plugins: samplePluginsInfo, 302 }, 303 }, 304 prettyGolden: "docker-info-plugins", 305 jsonGolden: "docker-info-plugins", 306 warningsGolden: "docker-info-plugins-warnings", 307 }, 308 { 309 doc: "info with nil labels", 310 dockerInfo: dockerInfo{ 311 Info: &sampleInfoLabelsNil, 312 ClientInfo: &clientInfo{clientVersion: clientVersion{Context: "default"}}, 313 }, 314 prettyGolden: "docker-info-with-labels-nil", 315 }, 316 { 317 doc: "info with empty labels", 318 dockerInfo: dockerInfo{ 319 Info: &sampleInfoLabelsEmpty, 320 ClientInfo: &clientInfo{clientVersion: clientVersion{Context: "default"}}, 321 }, 322 prettyGolden: "docker-info-with-labels-empty", 323 }, 324 { 325 doc: "info with swarm", 326 dockerInfo: dockerInfo{ 327 Info: &infoWithSwarm, 328 ClientInfo: &clientInfo{ 329 clientVersion: clientVersion{Context: "default"}, 330 Debug: false, 331 }, 332 }, 333 prettyGolden: "docker-info-with-swarm", 334 jsonGolden: "docker-info-with-swarm", 335 }, 336 { 337 doc: "info with daemon warnings", 338 dockerInfo: dockerInfo{ 339 Info: &sampleInfoDaemonWarnings, 340 ClientInfo: &clientInfo{ 341 clientVersion: clientVersion{ 342 Platform: &platformInfo{Name: "Docker Engine - Community"}, 343 Version: "24.0.0", 344 Context: "default", 345 }, 346 Debug: true, 347 }, 348 }, 349 prettyGolden: "docker-info-no-swarm", 350 warningsGolden: "docker-info-warnings", 351 jsonGolden: "docker-info-daemon-warnings", 352 }, 353 { 354 doc: "errors for both", 355 dockerInfo: dockerInfo{ 356 ServerErrors: []string{"a server error occurred"}, 357 ClientErrors: []string{"a client error occurred"}, 358 }, 359 prettyGolden: "docker-info-errors", 360 jsonGolden: "docker-info-errors", 361 warningsGolden: "docker-info-errors-stderr", 362 expectedError: "errors pretty printing info", 363 }, 364 { 365 doc: "bad security info", 366 dockerInfo: dockerInfo{ 367 Info: &sampleInfoBadSecurity, 368 ServerErrors: []string{"a server error occurred"}, 369 ClientInfo: &clientInfo{Debug: false}, 370 }, 371 prettyGolden: "docker-info-badsec", 372 jsonGolden: "docker-info-badsec", 373 warningsGolden: "docker-info-badsec-stderr", 374 expectedError: "errors pretty printing info", 375 }, 376 } { 377 tc := tc 378 t.Run(tc.doc, func(t *testing.T) { 379 cli := test.NewFakeCli(&fakeClient{}) 380 err := prettyPrintInfo(cli, tc.dockerInfo) 381 if tc.expectedError == "" { 382 assert.NilError(t, err) 383 } else { 384 assert.Error(t, err, tc.expectedError) 385 } 386 golden.Assert(t, cli.OutBuffer().String(), tc.prettyGolden+".golden") 387 if tc.warningsGolden != "" { 388 golden.Assert(t, cli.ErrBuffer().String(), tc.warningsGolden+".golden") 389 } else { 390 assert.Check(t, is.Equal("", cli.ErrBuffer().String())) 391 } 392 393 if tc.jsonGolden != "" { 394 cli = test.NewFakeCli(&fakeClient{}) 395 assert.NilError(t, formatInfo(cli.Out(), tc.dockerInfo, "{{json .}}")) 396 golden.Assert(t, cli.OutBuffer().String(), tc.jsonGolden+".json.golden") 397 assert.Check(t, is.Equal("", cli.ErrBuffer().String())) 398 399 cli = test.NewFakeCli(&fakeClient{}) 400 assert.NilError(t, formatInfo(cli.Out(), tc.dockerInfo, "json")) 401 golden.Assert(t, cli.OutBuffer().String(), tc.jsonGolden+".json.golden") 402 assert.Check(t, is.Equal("", cli.ErrBuffer().String())) 403 } 404 }) 405 } 406 } 407 408 func BenchmarkPrettyPrintInfo(b *testing.B) { 409 infoWithSwarm := sampleInfoNoSwarm 410 infoWithSwarm.Swarm = sampleSwarmInfo 411 412 info := dockerInfo{ 413 Info: &infoWithSwarm, 414 ClientInfo: &clientInfo{ 415 clientVersion: clientVersion{ 416 Platform: &platformInfo{Name: "Docker Engine - Community"}, 417 Version: "24.0.0", 418 Context: "default", 419 }, 420 Debug: true, 421 }, 422 } 423 cli := test.NewFakeCli(&fakeClient{}) 424 425 b.ReportAllocs() 426 for i := 0; i < b.N; i++ { 427 _ = prettyPrintInfo(cli, info) 428 cli.ResetOutputBuffers() 429 } 430 } 431 432 func TestFormatInfo(t *testing.T) { 433 for _, tc := range []struct { 434 doc string 435 template string 436 expectedError string 437 expectedOut string 438 }{ 439 { 440 doc: "basic", 441 template: "{{.ID}}", 442 expectedOut: sampleID + "\n", 443 }, 444 { 445 doc: "syntax", 446 template: "{{}", 447 expectedError: `Status: template parsing error: template: :1: unexpected "}" in command, Code: 64`, 448 }, 449 { 450 doc: "syntax", 451 template: "{{.badString}}", 452 expectedError: `template: :1:2: executing "" at <.badString>: can't evaluate field badString in type system.dockerInfo`, 453 }, 454 } { 455 tc := tc 456 t.Run(tc.doc, func(t *testing.T) { 457 cli := test.NewFakeCli(&fakeClient{}) 458 info := dockerInfo{ 459 Info: &sampleInfoNoSwarm, 460 ClientInfo: &clientInfo{Debug: true}, 461 } 462 err := formatInfo(cli.Out(), info, tc.template) 463 switch { 464 case tc.expectedOut != "": 465 assert.NilError(t, err) 466 assert.Equal(t, cli.OutBuffer().String(), tc.expectedOut) 467 case tc.expectedError != "": 468 assert.Error(t, err, tc.expectedError) 469 default: 470 t.Fatal("test expected to neither pass nor fail") 471 } 472 }) 473 } 474 } 475 476 func TestNeedsServerInfo(t *testing.T) { 477 tests := []struct { 478 doc string 479 template string 480 expected bool 481 }{ 482 { 483 doc: "no template", 484 template: "", 485 expected: true, 486 }, 487 { 488 doc: "JSON", 489 template: "json", 490 expected: true, 491 }, 492 { 493 doc: "JSON (all fields)", 494 template: "{{json .}}", 495 expected: true, 496 }, 497 { 498 doc: "JSON (Server ID)", 499 template: "{{json .ID}}", 500 expected: true, 501 }, 502 { 503 doc: "ClientInfo", 504 template: "{{json .ClientInfo}}", 505 expected: false, 506 }, 507 { 508 doc: "JSON ClientInfo", 509 template: "{{json .ClientInfo}}", 510 expected: false, 511 }, 512 { 513 doc: "JSON (Active context)", 514 template: "{{json .ClientInfo.Context}}", 515 expected: false, 516 }, 517 } 518 519 inf := dockerInfo{ClientInfo: &clientInfo{}} 520 for _, tc := range tests { 521 tc := tc 522 t.Run(tc.doc, func(t *testing.T) { 523 assert.Equal(t, needsServerInfo(tc.template, inf), tc.expected) 524 }) 525 } 526 }