github.com/xeptore/docker-cli@v20.10.14+incompatible/cli/command/system/info_test.go (about) 1 package system 2 3 import ( 4 "encoding/base64" 5 "net" 6 "testing" 7 "time" 8 9 pluginmanager "github.com/docker/cli/cli-plugins/manager" 10 "github.com/docker/cli/internal/test" 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/api/types/registry" 13 "github.com/docker/docker/api/types/swarm" 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 = types.Info{ 28 ID: sampleID, 29 Containers: 0, 30 ContainersRunning: 0, 31 ContainersPaused: 0, 32 ContainersStopped: 0, 33 Images: 0, 34 Driver: "aufs", 35 DriverStatus: [][2]string{ 36 {"Root Dir", "/var/lib/docker/aufs"}, 37 {"Backing Filesystem", "extfs"}, 38 {"Dirs", "0"}, 39 {"Dirperm1 Supported", "true"}, 40 }, 41 SystemStatus: nil, 42 Plugins: types.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", "logentries", "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: ®istry.ServiceConfig{ 73 AllowNondistributableArtifactsCIDRs: nil, 74 AllowNondistributableArtifactsHostnames: nil, 75 InsecureRegistryCIDRs: []*registry.NetIPNet{ 76 { 77 IP: net.ParseIP("127.0.0.0"), 78 Mask: net.IPv4Mask(255, 0, 0, 0), 79 }, 80 }, 81 IndexConfigs: map[string]*registry.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 ClusterStore: "", 102 ClusterAdvertise: "", 103 Runtimes: map[string]types.Runtime{ 104 "runc": { 105 Path: "docker-runc", 106 Args: nil, 107 }, 108 }, 109 DefaultRuntime: "runc", 110 Swarm: swarm.Info{LocalNodeState: "inactive"}, 111 LiveRestoreEnabled: false, 112 Isolation: "", 113 InitBinary: "docker-init", 114 ContainerdCommit: types.Commit{ 115 ID: "6e23458c129b551d5c9871e5174f6b1b7f6d1170", 116 Expected: "6e23458c129b551d5c9871e5174f6b1b7f6d1170", 117 }, 118 RuncCommit: types.Commit{ 119 ID: "810190ceaa507aa2727d7ae6f4790c76ec150bd2", 120 Expected: "810190ceaa507aa2727d7ae6f4790c76ec150bd2", 121 }, 122 InitCommit: types.Commit{ 123 ID: "949e6fa", 124 Expected: "949e6fa", 125 }, 126 SecurityOptions: []string{"name=apparmor", "name=seccomp,profile=default"}, 127 DefaultAddressPools: []types.NetworkAddressPool{ 128 { 129 Base: "10.123.0.0/16", 130 Size: 24, 131 }, 132 }, 133 } 134 135 var sampleSwarmInfo = swarm.Info{ 136 NodeID: "qo2dfdig9mmxqkawulggepdih", 137 NodeAddr: "165.227.107.89", 138 LocalNodeState: "active", 139 ControlAvailable: true, 140 Error: "", 141 RemoteManagers: []swarm.Peer{ 142 { 143 NodeID: "qo2dfdig9mmxqkawulggepdih", 144 Addr: "165.227.107.89:2377", 145 }, 146 }, 147 Nodes: 1, 148 Managers: 1, 149 Cluster: &swarm.ClusterInfo{ 150 ID: "9vs5ygs0gguyyec4iqf2314c0", 151 Meta: swarm.Meta{ 152 Version: swarm.Version{Index: 11}, 153 CreatedAt: time.Date(2017, 8, 24, 17, 34, 19, 278062352, time.UTC), 154 UpdatedAt: time.Date(2017, 8, 24, 17, 34, 42, 398815481, time.UTC), 155 }, 156 Spec: swarm.Spec{ 157 Annotations: swarm.Annotations{ 158 Name: "default", 159 Labels: nil, 160 }, 161 Orchestration: swarm.OrchestrationConfig{ 162 TaskHistoryRetentionLimit: &[]int64{5}[0], 163 }, 164 Raft: swarm.RaftConfig{ 165 SnapshotInterval: 10000, 166 KeepOldSnapshots: &[]uint64{0}[0], 167 LogEntriesForSlowFollowers: 500, 168 ElectionTick: 3, 169 HeartbeatTick: 1, 170 }, 171 Dispatcher: swarm.DispatcherConfig{ 172 HeartbeatPeriod: 5000000000, 173 }, 174 CAConfig: swarm.CAConfig{ 175 NodeCertExpiry: 7776000000000000, 176 }, 177 TaskDefaults: swarm.TaskDefaults{}, 178 EncryptionConfig: swarm.EncryptionConfig{ 179 AutoLockManagers: true, 180 }, 181 }, 182 TLSInfo: swarm.TLSInfo{ 183 TrustRoot: ` 184 -----BEGIN CERTIFICATE----- 185 MIIBajCCARCgAwIBAgIUaFCW5xsq8eyiJ+Pmcv3MCflMLnMwCgYIKoZIzj0EAwIw 186 EzERMA8GA1UEAxMIc3dhcm0tY2EwHhcNMTcwODI0MTcyOTAwWhcNMzcwODE5MTcy 187 OTAwWjATMREwDwYDVQQDEwhzd2FybS1jYTBZMBMGByqGSM49AgEGCCqGSM49AwEH 188 A0IABDy7NebyUJyUjWJDBUdnZoV6GBxEGKO4TZPNDwnxDxJcUdLVaB7WGa4/DLrW 189 UfsVgh1JGik2VTiLuTMA1tLlNPOjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB 190 Af8EBTADAQH/MB0GA1UdDgQWBBQl16XFtaaXiUAwEuJptJlDjfKskDAKBggqhkjO 191 PQQDAgNIADBFAiEAo9fTQNM5DP9bHVcTJYfl2Cay1bFu1E+lnpmN+EYJfeACIGKH 192 1pCUkZ+D0IB6CiEZGWSHyLuXPM1rlP+I5KuS7sB8 193 -----END CERTIFICATE----- 194 `, 195 CertIssuerSubject: base64Decode("MBMxETAPBgNVBAMTCHN3YXJtLWNh"), 196 CertIssuerPublicKey: base64Decode( 197 "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPLs15vJQnJSNYkMFR2dmhXoYHEQYo7hNk80PCfEPElxR0tVoHtYZrj8MutZR+xWCHUkaKTZVOIu5MwDW0uU08w=="), 198 }, 199 RootRotationInProgress: false, 200 }, 201 } 202 203 var samplePluginsInfo = []pluginmanager.Plugin{ 204 { 205 Name: "goodplugin", 206 Path: "/path/to/docker-goodplugin", 207 Metadata: pluginmanager.Metadata{ 208 SchemaVersion: "0.1.0", 209 ShortDescription: "unit test is good", 210 Vendor: "ACME Corp", 211 Version: "0.1.0", 212 }, 213 }, 214 { 215 Name: "unversionedplugin", 216 Path: "/path/to/docker-unversionedplugin", 217 Metadata: pluginmanager.Metadata{ 218 SchemaVersion: "0.1.0", 219 ShortDescription: "this plugin has no version", 220 Vendor: "ACME Corp", 221 }, 222 }, 223 { 224 Name: "badplugin", 225 Path: "/path/to/docker-badplugin", 226 Err: pluginmanager.NewPluginError("something wrong"), 227 }, 228 } 229 230 func TestPrettyPrintInfo(t *testing.T) { 231 infoWithSwarm := sampleInfoNoSwarm 232 infoWithSwarm.Swarm = sampleSwarmInfo 233 234 infoWithWarningsLinux := sampleInfoNoSwarm 235 infoWithWarningsLinux.MemoryLimit = false 236 infoWithWarningsLinux.SwapLimit = false 237 infoWithWarningsLinux.KernelMemory = false 238 infoWithWarningsLinux.OomKillDisable = false 239 infoWithWarningsLinux.CPUCfsQuota = false 240 infoWithWarningsLinux.CPUCfsPeriod = false 241 infoWithWarningsLinux.CPUShares = false 242 infoWithWarningsLinux.CPUSet = false 243 infoWithWarningsLinux.IPv4Forwarding = false 244 infoWithWarningsLinux.BridgeNfIptables = false 245 infoWithWarningsLinux.BridgeNfIP6tables = false 246 247 sampleInfoDaemonWarnings := sampleInfoNoSwarm 248 sampleInfoDaemonWarnings.Warnings = []string{ 249 "WARNING: No memory limit support", 250 "WARNING: No swap limit support", 251 "WARNING: No oom kill disable support", 252 "WARNING: No cpu cfs quota support", 253 "WARNING: No cpu cfs period support", 254 "WARNING: No cpu shares support", 255 "WARNING: No cpuset support", 256 "WARNING: IPv4 forwarding is disabled", 257 "WARNING: bridge-nf-call-iptables is disabled", 258 "WARNING: bridge-nf-call-ip6tables is disabled", 259 } 260 261 sampleInfoBadSecurity := sampleInfoNoSwarm 262 sampleInfoBadSecurity.SecurityOptions = []string{"foo="} 263 264 for _, tc := range []struct { 265 doc string 266 dockerInfo info 267 268 prettyGolden string 269 warningsGolden string 270 jsonGolden string 271 expectedError string 272 }{ 273 { 274 doc: "info without swarm", 275 dockerInfo: info{ 276 Info: &sampleInfoNoSwarm, 277 ClientInfo: &clientInfo{ 278 Context: "default", 279 Debug: true, 280 }, 281 }, 282 prettyGolden: "docker-info-no-swarm", 283 jsonGolden: "docker-info-no-swarm", 284 }, 285 { 286 doc: "info with plugins", 287 dockerInfo: info{ 288 Info: &sampleInfoNoSwarm, 289 ClientInfo: &clientInfo{ 290 Context: "default", 291 Plugins: samplePluginsInfo, 292 }, 293 }, 294 prettyGolden: "docker-info-plugins", 295 jsonGolden: "docker-info-plugins", 296 warningsGolden: "docker-info-plugins-warnings", 297 }, 298 { 299 300 doc: "info with swarm", 301 dockerInfo: info{ 302 Info: &infoWithSwarm, 303 ClientInfo: &clientInfo{ 304 Context: "default", 305 Debug: false, 306 }, 307 }, 308 prettyGolden: "docker-info-with-swarm", 309 jsonGolden: "docker-info-with-swarm", 310 }, 311 { 312 doc: "info with legacy warnings", 313 dockerInfo: info{ 314 Info: &infoWithWarningsLinux, 315 ClientInfo: &clientInfo{ 316 Context: "default", 317 Debug: true, 318 }, 319 }, 320 prettyGolden: "docker-info-no-swarm", 321 warningsGolden: "docker-info-warnings", 322 jsonGolden: "docker-info-legacy-warnings", 323 }, 324 { 325 doc: "info with daemon warnings", 326 dockerInfo: info{ 327 Info: &sampleInfoDaemonWarnings, 328 ClientInfo: &clientInfo{ 329 Context: "default", 330 Debug: true, 331 }, 332 }, 333 prettyGolden: "docker-info-no-swarm", 334 warningsGolden: "docker-info-warnings", 335 jsonGolden: "docker-info-daemon-warnings", 336 }, 337 { 338 doc: "errors for both", 339 dockerInfo: info{ 340 ServerErrors: []string{"a server error occurred"}, 341 ClientErrors: []string{"a client error occurred"}, 342 }, 343 prettyGolden: "docker-info-errors", 344 jsonGolden: "docker-info-errors", 345 expectedError: "errors pretty printing info", 346 }, 347 { 348 doc: "bad security info", 349 dockerInfo: info{ 350 Info: &sampleInfoBadSecurity, 351 ServerErrors: []string{"an error happened"}, 352 ClientInfo: &clientInfo{Debug: false}, 353 }, 354 prettyGolden: "docker-info-badsec", 355 jsonGolden: "docker-info-badsec", 356 expectedError: "errors pretty printing info", 357 }, 358 } { 359 t.Run(tc.doc, func(t *testing.T) { 360 cli := test.NewFakeCli(&fakeClient{}) 361 err := prettyPrintInfo(cli, tc.dockerInfo) 362 if tc.expectedError == "" { 363 assert.NilError(t, err) 364 } else { 365 assert.Error(t, err, tc.expectedError) 366 } 367 golden.Assert(t, cli.OutBuffer().String(), tc.prettyGolden+".golden") 368 if tc.warningsGolden != "" { 369 golden.Assert(t, cli.ErrBuffer().String(), tc.warningsGolden+".golden") 370 } else { 371 assert.Check(t, is.Equal("", cli.ErrBuffer().String())) 372 } 373 374 cli = test.NewFakeCli(&fakeClient{}) 375 assert.NilError(t, formatInfo(cli, tc.dockerInfo, "{{json .}}")) 376 golden.Assert(t, cli.OutBuffer().String(), tc.jsonGolden+".json.golden") 377 assert.Check(t, is.Equal("", cli.ErrBuffer().String())) 378 }) 379 } 380 } 381 382 func TestFormatInfo(t *testing.T) { 383 for _, tc := range []struct { 384 doc string 385 template string 386 expectedError string 387 expectedOut string 388 }{ 389 { 390 doc: "basic", 391 template: "{{.ID}}", 392 expectedOut: sampleID + "\n", 393 }, 394 { 395 doc: "syntax", 396 template: "{{}", 397 expectedError: `Status: Template parsing error: template: :1: unexpected "}" in command, Code: 64`, 398 }, 399 { 400 doc: "syntax", 401 template: "{{.badString}}", 402 expectedError: `template: :1:2: executing "" at <.badString>: can't evaluate field badString in type system.info`, 403 }, 404 } { 405 t.Run(tc.doc, func(t *testing.T) { 406 cli := test.NewFakeCli(&fakeClient{}) 407 info := info{ 408 Info: &sampleInfoNoSwarm, 409 ClientInfo: &clientInfo{Debug: true}, 410 } 411 err := formatInfo(cli, info, tc.template) 412 if tc.expectedOut != "" { 413 assert.NilError(t, err) 414 assert.Equal(t, cli.OutBuffer().String(), tc.expectedOut) 415 } else if tc.expectedError != "" { 416 assert.Error(t, err, tc.expectedError) 417 } else { 418 t.Fatal("test expected to neither pass nor fail") 419 } 420 }) 421 } 422 }