github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/cli/command/system/info.go (about) 1 package system 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "sort" 8 "strings" 9 10 "github.com/docker/cli/cli" 11 pluginmanager "github.com/docker/cli/cli-plugins/manager" 12 "github.com/docker/cli/cli/command" 13 "github.com/docker/cli/cli/debug" 14 "github.com/docker/cli/templates" 15 "github.com/docker/docker/api/types" 16 "github.com/docker/docker/api/types/swarm" 17 "github.com/docker/go-units" 18 "github.com/spf13/cobra" 19 ) 20 21 type infoOptions struct { 22 format string 23 } 24 25 type clientInfo struct { 26 Debug bool 27 Context string 28 Plugins []pluginmanager.Plugin 29 Warnings []string 30 } 31 32 type info struct { 33 // This field should/could be ServerInfo but is anonymous to 34 // preserve backwards compatibility in the JSON rendering 35 // which has ServerInfo immediately within the top-level 36 // object. 37 *types.Info `json:",omitempty"` 38 ServerErrors []string `json:",omitempty"` 39 40 ClientInfo *clientInfo `json:",omitempty"` 41 ClientErrors []string `json:",omitempty"` 42 } 43 44 // NewInfoCommand creates a new cobra.Command for `docker info` 45 func NewInfoCommand(dockerCli command.Cli) *cobra.Command { 46 var opts infoOptions 47 48 cmd := &cobra.Command{ 49 Use: "info [OPTIONS]", 50 Short: "Display system-wide information", 51 Args: cli.NoArgs, 52 RunE: func(cmd *cobra.Command, args []string) error { 53 return runInfo(cmd, dockerCli, &opts) 54 }, 55 } 56 57 flags := cmd.Flags() 58 59 flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template") 60 61 return cmd 62 } 63 64 func runInfo(cmd *cobra.Command, dockerCli command.Cli, opts *infoOptions) error { 65 var info info 66 67 ctx := context.Background() 68 if dinfo, err := dockerCli.Client().Info(ctx); err == nil { 69 info.Info = &dinfo 70 } else { 71 info.ServerErrors = append(info.ServerErrors, err.Error()) 72 } 73 74 info.ClientInfo = &clientInfo{ 75 Context: dockerCli.CurrentContext(), 76 Debug: debug.IsEnabled(), 77 } 78 if plugins, err := pluginmanager.ListPlugins(dockerCli, cmd.Root()); err == nil { 79 info.ClientInfo.Plugins = plugins 80 } else { 81 info.ClientErrors = append(info.ClientErrors, err.Error()) 82 } 83 84 if opts.format == "" { 85 return prettyPrintInfo(dockerCli, info) 86 } 87 return formatInfo(dockerCli, info, opts.format) 88 } 89 90 func prettyPrintInfo(dockerCli command.Cli, info info) error { 91 fmt.Fprintln(dockerCli.Out(), "Client:") 92 if info.ClientInfo != nil { 93 prettyPrintClientInfo(dockerCli, *info.ClientInfo) 94 } 95 for _, err := range info.ClientErrors { 96 fmt.Fprintln(dockerCli.Out(), "ERROR:", err) 97 } 98 99 fmt.Fprintln(dockerCli.Out()) 100 fmt.Fprintln(dockerCli.Out(), "Server:") 101 if info.Info != nil { 102 for _, err := range prettyPrintServerInfo(dockerCli, *info.Info) { 103 info.ServerErrors = append(info.ServerErrors, err.Error()) 104 } 105 } 106 for _, err := range info.ServerErrors { 107 fmt.Fprintln(dockerCli.Out(), "ERROR:", err) 108 } 109 110 if len(info.ServerErrors) > 0 || len(info.ClientErrors) > 0 { 111 return fmt.Errorf("errors pretty printing info") 112 } 113 return nil 114 } 115 116 func prettyPrintClientInfo(dockerCli command.Cli, info clientInfo) { 117 fmt.Fprintln(dockerCli.Out(), " Context: ", info.Context) 118 fmt.Fprintln(dockerCli.Out(), " Debug Mode:", info.Debug) 119 120 if len(info.Plugins) > 0 { 121 fmt.Fprintln(dockerCli.Out(), " Plugins:") 122 for _, p := range info.Plugins { 123 if p.Err == nil { 124 var version string 125 if p.Version != "" { 126 version = ", " + p.Version 127 } 128 fmt.Fprintf(dockerCli.Out(), " %s: %s (%s%s)\n", p.Name, p.ShortDescription, p.Vendor, version) 129 } else { 130 info.Warnings = append(info.Warnings, fmt.Sprintf("WARNING: Plugin %q is not valid: %s", p.Path, p.Err)) 131 } 132 } 133 } 134 135 if len(info.Warnings) > 0 { 136 fmt.Fprintln(dockerCli.Err(), strings.Join(info.Warnings, "\n")) 137 } 138 } 139 140 // nolint: gocyclo 141 func prettyPrintServerInfo(dockerCli command.Cli, info types.Info) []error { 142 var errs []error 143 144 fmt.Fprintln(dockerCli.Out(), " Containers:", info.Containers) 145 fmt.Fprintln(dockerCli.Out(), " Running:", info.ContainersRunning) 146 fmt.Fprintln(dockerCli.Out(), " Paused:", info.ContainersPaused) 147 fmt.Fprintln(dockerCli.Out(), " Stopped:", info.ContainersStopped) 148 fmt.Fprintln(dockerCli.Out(), " Images:", info.Images) 149 fprintlnNonEmpty(dockerCli.Out(), " Server Version:", info.ServerVersion) 150 fprintlnNonEmpty(dockerCli.Out(), " Storage Driver:", info.Driver) 151 if info.DriverStatus != nil { 152 for _, pair := range info.DriverStatus { 153 fmt.Fprintf(dockerCli.Out(), " %s: %s\n", pair[0], pair[1]) 154 } 155 } 156 if info.SystemStatus != nil { 157 for _, pair := range info.SystemStatus { 158 fmt.Fprintf(dockerCli.Out(), " %s: %s\n", pair[0], pair[1]) 159 } 160 } 161 fprintlnNonEmpty(dockerCli.Out(), " Logging Driver:", info.LoggingDriver) 162 fprintlnNonEmpty(dockerCli.Out(), " Cgroup Driver:", info.CgroupDriver) 163 fprintlnNonEmpty(dockerCli.Out(), " Cgroup Version:", info.CgroupVersion) 164 165 fmt.Fprintln(dockerCli.Out(), " Plugins:") 166 fmt.Fprintln(dockerCli.Out(), " Volume:", strings.Join(info.Plugins.Volume, " ")) 167 fmt.Fprintln(dockerCli.Out(), " Network:", strings.Join(info.Plugins.Network, " ")) 168 169 if len(info.Plugins.Authorization) != 0 { 170 fmt.Fprintln(dockerCli.Out(), " Authorization:", strings.Join(info.Plugins.Authorization, " ")) 171 } 172 173 fmt.Fprintln(dockerCli.Out(), " Log:", strings.Join(info.Plugins.Log, " ")) 174 175 fmt.Fprintln(dockerCli.Out(), " Swarm:", info.Swarm.LocalNodeState) 176 printSwarmInfo(dockerCli, info) 177 178 if len(info.Runtimes) > 0 { 179 fmt.Fprint(dockerCli.Out(), " Runtimes:") 180 for name := range info.Runtimes { 181 fmt.Fprintf(dockerCli.Out(), " %s", name) 182 } 183 fmt.Fprint(dockerCli.Out(), "\n") 184 fmt.Fprintln(dockerCli.Out(), " Default Runtime:", info.DefaultRuntime) 185 } 186 187 if info.OSType == "linux" { 188 fmt.Fprintln(dockerCli.Out(), " Init Binary:", info.InitBinary) 189 190 for _, ci := range []struct { 191 Name string 192 Commit types.Commit 193 }{ 194 {"containerd", info.ContainerdCommit}, 195 {"runc", info.RuncCommit}, 196 {"init", info.InitCommit}, 197 } { 198 fmt.Fprintf(dockerCli.Out(), " %s version: %s", ci.Name, ci.Commit.ID) 199 if ci.Commit.ID != ci.Commit.Expected { 200 fmt.Fprintf(dockerCli.Out(), " (expected: %s)", ci.Commit.Expected) 201 } 202 fmt.Fprint(dockerCli.Out(), "\n") 203 } 204 if len(info.SecurityOptions) != 0 { 205 if kvs, err := types.DecodeSecurityOptions(info.SecurityOptions); err != nil { 206 errs = append(errs, err) 207 } else { 208 fmt.Fprintln(dockerCli.Out(), " Security Options:") 209 for _, so := range kvs { 210 fmt.Fprintln(dockerCli.Out(), " "+so.Name) 211 for _, o := range so.Options { 212 switch o.Key { 213 case "profile": 214 if o.Value != "default" { 215 fmt.Fprintln(dockerCli.Err(), " WARNING: You're not using the default seccomp profile") 216 } 217 fmt.Fprintln(dockerCli.Out(), " Profile:", o.Value) 218 } 219 } 220 } 221 } 222 } 223 } 224 225 // Isolation only has meaning on a Windows daemon. 226 if info.OSType == "windows" { 227 fmt.Fprintln(dockerCli.Out(), " Default Isolation:", info.Isolation) 228 } 229 230 fprintlnNonEmpty(dockerCli.Out(), " Kernel Version:", info.KernelVersion) 231 fprintlnNonEmpty(dockerCli.Out(), " Operating System:", info.OperatingSystem) 232 fprintlnNonEmpty(dockerCli.Out(), " OSType:", info.OSType) 233 fprintlnNonEmpty(dockerCli.Out(), " Architecture:", info.Architecture) 234 fmt.Fprintln(dockerCli.Out(), " CPUs:", info.NCPU) 235 fmt.Fprintln(dockerCli.Out(), " Total Memory:", units.BytesSize(float64(info.MemTotal))) 236 fprintlnNonEmpty(dockerCli.Out(), " Name:", info.Name) 237 fprintlnNonEmpty(dockerCli.Out(), " ID:", info.ID) 238 fmt.Fprintln(dockerCli.Out(), " Docker Root Dir:", info.DockerRootDir) 239 fmt.Fprintln(dockerCli.Out(), " Debug Mode:", info.Debug) 240 241 if info.Debug { 242 fmt.Fprintln(dockerCli.Out(), " File Descriptors:", info.NFd) 243 fmt.Fprintln(dockerCli.Out(), " Goroutines:", info.NGoroutines) 244 fmt.Fprintln(dockerCli.Out(), " System Time:", info.SystemTime) 245 fmt.Fprintln(dockerCli.Out(), " EventsListeners:", info.NEventsListener) 246 } 247 248 fprintlnNonEmpty(dockerCli.Out(), " HTTP Proxy:", info.HTTPProxy) 249 fprintlnNonEmpty(dockerCli.Out(), " HTTPS Proxy:", info.HTTPSProxy) 250 fprintlnNonEmpty(dockerCli.Out(), " No Proxy:", info.NoProxy) 251 252 if info.IndexServerAddress != "" { 253 u := dockerCli.ConfigFile().AuthConfigs[info.IndexServerAddress].Username 254 if len(u) > 0 { 255 fmt.Fprintln(dockerCli.Out(), " Username:", u) 256 } 257 fmt.Fprintln(dockerCli.Out(), " Registry:", info.IndexServerAddress) 258 } 259 260 if info.Labels != nil { 261 fmt.Fprintln(dockerCli.Out(), " Labels:") 262 for _, lbl := range info.Labels { 263 fmt.Fprintln(dockerCli.Out(), " "+lbl) 264 } 265 } 266 267 fmt.Fprintln(dockerCli.Out(), " Experimental:", info.ExperimentalBuild) 268 fprintlnNonEmpty(dockerCli.Out(), " Cluster Store:", info.ClusterStore) 269 fprintlnNonEmpty(dockerCli.Out(), " Cluster Advertise:", info.ClusterAdvertise) 270 271 if info.RegistryConfig != nil && (len(info.RegistryConfig.InsecureRegistryCIDRs) > 0 || len(info.RegistryConfig.IndexConfigs) > 0) { 272 fmt.Fprintln(dockerCli.Out(), " Insecure Registries:") 273 for _, registry := range info.RegistryConfig.IndexConfigs { 274 if !registry.Secure { 275 fmt.Fprintln(dockerCli.Out(), " "+registry.Name) 276 } 277 } 278 279 for _, registry := range info.RegistryConfig.InsecureRegistryCIDRs { 280 mask, _ := registry.Mask.Size() 281 fmt.Fprintf(dockerCli.Out(), " %s/%d\n", registry.IP.String(), mask) 282 } 283 } 284 285 if info.RegistryConfig != nil && len(info.RegistryConfig.Mirrors) > 0 { 286 fmt.Fprintln(dockerCli.Out(), " Registry Mirrors:") 287 for _, mirror := range info.RegistryConfig.Mirrors { 288 fmt.Fprintln(dockerCli.Out(), " "+mirror) 289 } 290 } 291 292 fmt.Fprintln(dockerCli.Out(), " Live Restore Enabled:", info.LiveRestoreEnabled) 293 if info.ProductLicense != "" { 294 fmt.Fprintln(dockerCli.Out(), " Product License:", info.ProductLicense) 295 } 296 297 if info.DefaultAddressPools != nil && len(info.DefaultAddressPools) > 0 { 298 fmt.Fprintln(dockerCli.Out(), " Default Address Pools:") 299 for _, pool := range info.DefaultAddressPools { 300 fmt.Fprintf(dockerCli.Out(), " Base: %s, Size: %d\n", pool.Base, pool.Size) 301 } 302 } 303 304 fmt.Fprint(dockerCli.Out(), "\n") 305 306 printServerWarnings(dockerCli, info) 307 return errs 308 } 309 310 // nolint: gocyclo 311 func printSwarmInfo(dockerCli command.Cli, info types.Info) { 312 if info.Swarm.LocalNodeState == swarm.LocalNodeStateInactive || info.Swarm.LocalNodeState == swarm.LocalNodeStateLocked { 313 return 314 } 315 fmt.Fprintln(dockerCli.Out(), " NodeID:", info.Swarm.NodeID) 316 if info.Swarm.Error != "" { 317 fmt.Fprintln(dockerCli.Out(), " Error:", info.Swarm.Error) 318 } 319 fmt.Fprintln(dockerCli.Out(), " Is Manager:", info.Swarm.ControlAvailable) 320 if info.Swarm.Cluster != nil && info.Swarm.ControlAvailable && info.Swarm.Error == "" && info.Swarm.LocalNodeState != swarm.LocalNodeStateError { 321 fmt.Fprintln(dockerCli.Out(), " ClusterID:", info.Swarm.Cluster.ID) 322 fmt.Fprintln(dockerCli.Out(), " Managers:", info.Swarm.Managers) 323 fmt.Fprintln(dockerCli.Out(), " Nodes:", info.Swarm.Nodes) 324 var strAddrPool strings.Builder 325 if info.Swarm.Cluster.DefaultAddrPool != nil { 326 for _, p := range info.Swarm.Cluster.DefaultAddrPool { 327 strAddrPool.WriteString(p + " ") 328 } 329 fmt.Fprintln(dockerCli.Out(), " Default Address Pool:", strAddrPool.String()) 330 fmt.Fprintln(dockerCli.Out(), " SubnetSize:", info.Swarm.Cluster.SubnetSize) 331 } 332 if info.Swarm.Cluster.DataPathPort > 0 { 333 fmt.Fprintln(dockerCli.Out(), " Data Path Port:", info.Swarm.Cluster.DataPathPort) 334 } 335 fmt.Fprintln(dockerCli.Out(), " Orchestration:") 336 337 taskHistoryRetentionLimit := int64(0) 338 if info.Swarm.Cluster.Spec.Orchestration.TaskHistoryRetentionLimit != nil { 339 taskHistoryRetentionLimit = *info.Swarm.Cluster.Spec.Orchestration.TaskHistoryRetentionLimit 340 } 341 fmt.Fprintln(dockerCli.Out(), " Task History Retention Limit:", taskHistoryRetentionLimit) 342 fmt.Fprintln(dockerCli.Out(), " Raft:") 343 fmt.Fprintln(dockerCli.Out(), " Snapshot Interval:", info.Swarm.Cluster.Spec.Raft.SnapshotInterval) 344 if info.Swarm.Cluster.Spec.Raft.KeepOldSnapshots != nil { 345 fmt.Fprintf(dockerCli.Out(), " Number of Old Snapshots to Retain: %d\n", *info.Swarm.Cluster.Spec.Raft.KeepOldSnapshots) 346 } 347 fmt.Fprintln(dockerCli.Out(), " Heartbeat Tick:", info.Swarm.Cluster.Spec.Raft.HeartbeatTick) 348 fmt.Fprintln(dockerCli.Out(), " Election Tick:", info.Swarm.Cluster.Spec.Raft.ElectionTick) 349 fmt.Fprintln(dockerCli.Out(), " Dispatcher:") 350 fmt.Fprintln(dockerCli.Out(), " Heartbeat Period:", units.HumanDuration(info.Swarm.Cluster.Spec.Dispatcher.HeartbeatPeriod)) 351 fmt.Fprintln(dockerCli.Out(), " CA Configuration:") 352 fmt.Fprintln(dockerCli.Out(), " Expiry Duration:", units.HumanDuration(info.Swarm.Cluster.Spec.CAConfig.NodeCertExpiry)) 353 fmt.Fprintln(dockerCli.Out(), " Force Rotate:", info.Swarm.Cluster.Spec.CAConfig.ForceRotate) 354 if caCert := strings.TrimSpace(info.Swarm.Cluster.Spec.CAConfig.SigningCACert); caCert != "" { 355 fmt.Fprintf(dockerCli.Out(), " Signing CA Certificate: \n%s\n\n", caCert) 356 } 357 if len(info.Swarm.Cluster.Spec.CAConfig.ExternalCAs) > 0 { 358 fmt.Fprintln(dockerCli.Out(), " External CAs:") 359 for _, entry := range info.Swarm.Cluster.Spec.CAConfig.ExternalCAs { 360 fmt.Fprintf(dockerCli.Out(), " %s: %s\n", entry.Protocol, entry.URL) 361 } 362 } 363 fmt.Fprintln(dockerCli.Out(), " Autolock Managers:", info.Swarm.Cluster.Spec.EncryptionConfig.AutoLockManagers) 364 fmt.Fprintln(dockerCli.Out(), " Root Rotation In Progress:", info.Swarm.Cluster.RootRotationInProgress) 365 } 366 fmt.Fprintln(dockerCli.Out(), " Node Address:", info.Swarm.NodeAddr) 367 if len(info.Swarm.RemoteManagers) > 0 { 368 managers := []string{} 369 for _, entry := range info.Swarm.RemoteManagers { 370 managers = append(managers, entry.Addr) 371 } 372 sort.Strings(managers) 373 fmt.Fprintln(dockerCli.Out(), " Manager Addresses:") 374 for _, entry := range managers { 375 fmt.Fprintf(dockerCli.Out(), " %s\n", entry) 376 } 377 } 378 } 379 380 func printServerWarnings(dockerCli command.Cli, info types.Info) { 381 if len(info.Warnings) > 0 { 382 fmt.Fprintln(dockerCli.Err(), strings.Join(info.Warnings, "\n")) 383 return 384 } 385 // daemon didn't return warnings. Fallback to old behavior 386 printStorageDriverWarnings(dockerCli, info) 387 printServerWarningsLegacy(dockerCli, info) 388 } 389 390 // printServerWarningsLegacy generates warnings based on information returned by the daemon. 391 // DEPRECATED: warnings are now generated by the daemon, and returned in 392 // info.Warnings. This function is used to provide backward compatibility with 393 // daemons that do not provide these warnings. No new warnings should be added 394 // here. 395 func printServerWarningsLegacy(dockerCli command.Cli, info types.Info) { 396 if info.OSType == "windows" { 397 return 398 } 399 if !info.MemoryLimit { 400 fmt.Fprintln(dockerCli.Err(), "WARNING: No memory limit support") 401 } 402 if !info.SwapLimit { 403 fmt.Fprintln(dockerCli.Err(), "WARNING: No swap limit support") 404 } 405 if !info.KernelMemory { 406 fmt.Fprintln(dockerCli.Err(), "WARNING: No kernel memory limit support") 407 } 408 if !info.OomKillDisable { 409 fmt.Fprintln(dockerCli.Err(), "WARNING: No oom kill disable support") 410 } 411 if !info.CPUCfsQuota { 412 fmt.Fprintln(dockerCli.Err(), "WARNING: No cpu cfs quota support") 413 } 414 if !info.CPUCfsPeriod { 415 fmt.Fprintln(dockerCli.Err(), "WARNING: No cpu cfs period support") 416 } 417 if !info.CPUShares { 418 fmt.Fprintln(dockerCli.Err(), "WARNING: No cpu shares support") 419 } 420 if !info.CPUSet { 421 fmt.Fprintln(dockerCli.Err(), "WARNING: No cpuset support") 422 } 423 if !info.IPv4Forwarding { 424 fmt.Fprintln(dockerCli.Err(), "WARNING: IPv4 forwarding is disabled") 425 } 426 if !info.BridgeNfIptables { 427 fmt.Fprintln(dockerCli.Err(), "WARNING: bridge-nf-call-iptables is disabled") 428 } 429 if !info.BridgeNfIP6tables { 430 fmt.Fprintln(dockerCli.Err(), "WARNING: bridge-nf-call-ip6tables is disabled") 431 } 432 } 433 434 // printStorageDriverWarnings generates warnings based on storage-driver information 435 // returned by the daemon. 436 // DEPRECATED: warnings are now generated by the daemon, and returned in 437 // info.Warnings. This function is used to provide backward compatibility with 438 // daemons that do not provide these warnings. No new warnings should be added 439 // here. 440 func printStorageDriverWarnings(dockerCli command.Cli, info types.Info) { 441 if info.OSType == "windows" { 442 return 443 } 444 if info.DriverStatus == nil { 445 return 446 } 447 for _, pair := range info.DriverStatus { 448 if pair[0] == "Data loop file" { 449 fmt.Fprintf(dockerCli.Err(), "WARNING: %s: usage of loopback devices is "+ 450 "strongly discouraged for production use.\n "+ 451 "Use `--storage-opt dm.thinpooldev` to specify a custom block storage device.\n", info.Driver) 452 } 453 if pair[0] == "Supports d_type" && pair[1] == "false" { 454 backingFs := getBackingFs(info) 455 456 msg := fmt.Sprintf("WARNING: %s: the backing %s filesystem is formatted without d_type support, which leads to incorrect behavior.\n", info.Driver, backingFs) 457 if backingFs == "xfs" { 458 msg += " Reformat the filesystem with ftype=1 to enable d_type support.\n" 459 } 460 msg += " Running without d_type support will not be supported in future releases." 461 fmt.Fprintln(dockerCli.Err(), msg) 462 } 463 } 464 } 465 466 func getBackingFs(info types.Info) string { 467 if info.DriverStatus == nil { 468 return "" 469 } 470 471 for _, pair := range info.DriverStatus { 472 if pair[0] == "Backing Filesystem" { 473 return pair[1] 474 } 475 } 476 return "" 477 } 478 479 func formatInfo(dockerCli command.Cli, info info, format string) error { 480 // Ensure slice/array fields render as `[]` not `null` 481 if info.ClientInfo != nil && info.ClientInfo.Plugins == nil { 482 info.ClientInfo.Plugins = make([]pluginmanager.Plugin, 0) 483 } 484 485 tmpl, err := templates.Parse(format) 486 if err != nil { 487 return cli.StatusError{StatusCode: 64, 488 Status: "Template parsing error: " + err.Error()} 489 } 490 err = tmpl.Execute(dockerCli.Out(), info) 491 dockerCli.Out().Write([]byte{'\n'}) 492 return err 493 } 494 495 func fprintlnNonEmpty(w io.Writer, label, value string) { 496 if value != "" { 497 fmt.Fprintln(w, label, value) 498 } 499 }