github.com/cspotcode/docker-cli@v20.10.0-rc1.0.20201201121459-3faad7acc5b8+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 kernel memory 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 for _, tc := range []struct { 266 doc string 267 dockerInfo info 268 269 prettyGolden string 270 warningsGolden string 271 jsonGolden string 272 expectedError string 273 }{ 274 { 275 doc: "info without swarm", 276 dockerInfo: info{ 277 Info: &sampleInfoNoSwarm, 278 ClientInfo: &clientInfo{ 279 Context: "default", 280 Debug: true, 281 }, 282 }, 283 prettyGolden: "docker-info-no-swarm", 284 jsonGolden: "docker-info-no-swarm", 285 }, 286 { 287 doc: "info with plugins", 288 dockerInfo: info{ 289 Info: &sampleInfoNoSwarm, 290 ClientInfo: &clientInfo{ 291 Context: "default", 292 Plugins: samplePluginsInfo, 293 }, 294 }, 295 prettyGolden: "docker-info-plugins", 296 jsonGolden: "docker-info-plugins", 297 warningsGolden: "docker-info-plugins-warnings", 298 }, 299 { 300 301 doc: "info with swarm", 302 dockerInfo: info{ 303 Info: &infoWithSwarm, 304 ClientInfo: &clientInfo{ 305 Context: "default", 306 Debug: false, 307 }, 308 }, 309 prettyGolden: "docker-info-with-swarm", 310 jsonGolden: "docker-info-with-swarm", 311 }, 312 { 313 doc: "info with legacy warnings", 314 dockerInfo: info{ 315 Info: &infoWithWarningsLinux, 316 ClientInfo: &clientInfo{ 317 Context: "default", 318 Debug: true, 319 }, 320 }, 321 prettyGolden: "docker-info-no-swarm", 322 warningsGolden: "docker-info-warnings", 323 jsonGolden: "docker-info-legacy-warnings", 324 }, 325 { 326 doc: "info with daemon warnings", 327 dockerInfo: info{ 328 Info: &sampleInfoDaemonWarnings, 329 ClientInfo: &clientInfo{ 330 Context: "default", 331 Debug: true, 332 }, 333 }, 334 prettyGolden: "docker-info-no-swarm", 335 warningsGolden: "docker-info-warnings", 336 jsonGolden: "docker-info-daemon-warnings", 337 }, 338 { 339 doc: "errors for both", 340 dockerInfo: info{ 341 ServerErrors: []string{"a server error occurred"}, 342 ClientErrors: []string{"a client error occurred"}, 343 }, 344 prettyGolden: "docker-info-errors", 345 jsonGolden: "docker-info-errors", 346 expectedError: "errors pretty printing info", 347 }, 348 { 349 doc: "bad security info", 350 dockerInfo: info{ 351 Info: &sampleInfoBadSecurity, 352 ServerErrors: []string{"an error happened"}, 353 ClientInfo: &clientInfo{Debug: false}, 354 }, 355 prettyGolden: "docker-info-badsec", 356 jsonGolden: "docker-info-badsec", 357 expectedError: "errors pretty printing info", 358 }, 359 } { 360 t.Run(tc.doc, func(t *testing.T) { 361 cli := test.NewFakeCli(&fakeClient{}) 362 err := prettyPrintInfo(cli, tc.dockerInfo) 363 if tc.expectedError == "" { 364 assert.NilError(t, err) 365 } else { 366 assert.Error(t, err, tc.expectedError) 367 } 368 golden.Assert(t, cli.OutBuffer().String(), tc.prettyGolden+".golden") 369 if tc.warningsGolden != "" { 370 golden.Assert(t, cli.ErrBuffer().String(), tc.warningsGolden+".golden") 371 } else { 372 assert.Check(t, is.Equal("", cli.ErrBuffer().String())) 373 } 374 375 cli = test.NewFakeCli(&fakeClient{}) 376 assert.NilError(t, formatInfo(cli, tc.dockerInfo, "{{json .}}")) 377 golden.Assert(t, cli.OutBuffer().String(), tc.jsonGolden+".json.golden") 378 assert.Check(t, is.Equal("", cli.ErrBuffer().String())) 379 }) 380 } 381 } 382 383 func TestFormatInfo(t *testing.T) { 384 for _, tc := range []struct { 385 doc string 386 template string 387 expectedError string 388 expectedOut string 389 }{ 390 { 391 doc: "basic", 392 template: "{{.ID}}", 393 expectedOut: sampleID + "\n", 394 }, 395 { 396 doc: "syntax", 397 template: "{{}", 398 expectedError: `Status: Template parsing error: template: :1: unexpected "}" in command, Code: 64`, 399 }, 400 { 401 doc: "syntax", 402 template: "{{.badString}}", 403 expectedError: `template: :1:2: executing "" at <.badString>: can't evaluate field badString in type system.info`, 404 }, 405 } { 406 t.Run(tc.doc, func(t *testing.T) { 407 cli := test.NewFakeCli(&fakeClient{}) 408 info := info{ 409 Info: &sampleInfoNoSwarm, 410 ClientInfo: &clientInfo{Debug: true}, 411 } 412 err := formatInfo(cli, info, tc.template) 413 if tc.expectedOut != "" { 414 assert.NilError(t, err) 415 assert.Equal(t, cli.OutBuffer().String(), tc.expectedOut) 416 } else if tc.expectedError != "" { 417 assert.Error(t, err, tc.expectedError) 418 } else { 419 t.Fatal("test expected to neither pass nor fail") 420 } 421 }) 422 } 423 }