cli/command/system: add utilities for printing

Adding some utilities to print the output, to keep the linters happier
without having to either suppress errors, or ignore them.

Perhaps we should consider adding utilities for this on the "command.Streams"
outputs.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2023-04-10 16:48:22 +02:00
parent 1e89037d72
commit 155f7d9e2b
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
2 changed files with 119 additions and 112 deletions

View File

@ -108,7 +108,7 @@ func runInfo(cmd *cobra.Command, dockerCli command.Cli, opts *infoOptions) error
} else { } else {
// if a format is provided, print the error, as it may be hidden // if a format is provided, print the error, as it may be hidden
// otherwise if the template doesn't include the ServerErrors field. // otherwise if the template doesn't include the ServerErrors field.
fmt.Fprintln(dockerCli.Err(), err) fprintln(dockerCli.Err(), err)
} }
} }
} }
@ -118,7 +118,7 @@ func runInfo(cmd *cobra.Command, dockerCli command.Cli, opts *infoOptions) error
info.ClientInfo.APIVersion = dockerCli.CurrentVersion() info.ClientInfo.APIVersion = dockerCli.CurrentVersion()
return prettyPrintInfo(dockerCli, info) return prettyPrintInfo(dockerCli, info)
} }
return formatInfo(dockerCli, info, opts.format) return formatInfo(dockerCli.Out(), info, opts.format)
} }
// placeHolders does a rudimentary match for possible placeholders in a // placeHolders does a rudimentary match for possible placeholders in a
@ -163,26 +163,26 @@ func needsServerInfo(template string, info info) bool {
func prettyPrintInfo(streams command.Streams, info info) error { func prettyPrintInfo(streams command.Streams, info info) error {
// Only append the platform info if it's not empty, to prevent printing a trailing space. // Only append the platform info if it's not empty, to prevent printing a trailing space.
if p := info.clientPlatform(); p != "" { if p := info.clientPlatform(); p != "" {
_, _ = fmt.Fprintln(streams.Out(), "Client:", p) fprintln(streams.Out(), "Client:", p)
} else { } else {
_, _ = fmt.Fprintln(streams.Out(), "Client:") fprintln(streams.Out(), "Client:")
} }
if info.ClientInfo != nil { if info.ClientInfo != nil {
prettyPrintClientInfo(streams, *info.ClientInfo) prettyPrintClientInfo(streams, *info.ClientInfo)
} }
for _, err := range info.ClientErrors { for _, err := range info.ClientErrors {
fmt.Fprintln(streams.Err(), "ERROR:", err) fprintln(streams.Err(), "ERROR:", err)
} }
fmt.Fprintln(streams.Out()) fprintln(streams.Out())
fmt.Fprintln(streams.Out(), "Server:") fprintln(streams.Out(), "Server:")
if info.Info != nil { if info.Info != nil {
for _, err := range prettyPrintServerInfo(streams, &info) { for _, err := range prettyPrintServerInfo(streams, &info) {
info.ServerErrors = append(info.ServerErrors, err.Error()) info.ServerErrors = append(info.ServerErrors, err.Error())
} }
} }
for _, err := range info.ServerErrors { for _, err := range info.ServerErrors {
fmt.Fprintln(streams.Err(), "ERROR:", err) fprintln(streams.Err(), "ERROR:", err)
} }
if len(info.ServerErrors) > 0 || len(info.ClientErrors) > 0 { if len(info.ServerErrors) > 0 || len(info.ClientErrors) > 0 {
@ -192,18 +192,17 @@ func prettyPrintInfo(streams command.Streams, info info) error {
} }
func prettyPrintClientInfo(streams command.Streams, info clientInfo) { func prettyPrintClientInfo(streams command.Streams, info clientInfo) {
output := streams.Out() fprintlnNonEmpty(streams.Out(), " Version: ", info.Version)
fprintlnNonEmpty(output, " Version: ", info.Version) fprintln(streams.Out(), " Context: ", info.Context)
fmt.Fprintln(output, " Context: ", info.Context) fprintln(streams.Out(), " Debug Mode:", info.Debug)
fmt.Fprintln(output, " Debug Mode:", info.Debug)
if len(info.Plugins) > 0 { if len(info.Plugins) > 0 {
fmt.Fprintln(output, " Plugins:") fprintln(streams.Out(), " Plugins:")
for _, p := range info.Plugins { for _, p := range info.Plugins {
if p.Err == nil { if p.Err == nil {
fmt.Fprintf(output, " %s: %s (%s)\n", p.Name, p.ShortDescription, p.Vendor) fprintf(streams.Out(), " %s: %s (%s)\n", p.Name, p.ShortDescription, p.Vendor)
fprintlnNonEmpty(output, " Version: ", p.Version) fprintlnNonEmpty(streams.Out(), " Version: ", p.Version)
fprintlnNonEmpty(output, " Path: ", p.Path) fprintlnNonEmpty(streams.Out(), " Path: ", p.Path)
} else { } else {
info.Warnings = append(info.Warnings, fmt.Sprintf("WARNING: Plugin %q is not valid: %s", p.Path, p.Err)) info.Warnings = append(info.Warnings, fmt.Sprintf("WARNING: Plugin %q is not valid: %s", p.Path, p.Err))
} }
@ -211,7 +210,7 @@ func prettyPrintClientInfo(streams command.Streams, info clientInfo) {
} }
if len(info.Warnings) > 0 { if len(info.Warnings) > 0 {
fmt.Fprintln(streams.Err(), strings.Join(info.Warnings, "\n")) fprintln(streams.Err(), strings.Join(info.Warnings, "\n"))
} }
} }
@ -220,38 +219,38 @@ func prettyPrintServerInfo(streams command.Streams, info *info) []error {
var errs []error var errs []error
output := streams.Out() output := streams.Out()
fmt.Fprintln(output, " Containers:", info.Containers) fprintln(output, " Containers:", info.Containers)
fmt.Fprintln(output, " Running:", info.ContainersRunning) fprintln(output, " Running:", info.ContainersRunning)
fmt.Fprintln(output, " Paused:", info.ContainersPaused) fprintln(output, " Paused:", info.ContainersPaused)
fmt.Fprintln(output, " Stopped:", info.ContainersStopped) fprintln(output, " Stopped:", info.ContainersStopped)
fmt.Fprintln(output, " Images:", info.Images) fprintln(output, " Images:", info.Images)
fprintlnNonEmpty(output, " Server Version:", info.ServerVersion) fprintlnNonEmpty(output, " Server Version:", info.ServerVersion)
fprintlnNonEmpty(output, " Storage Driver:", info.Driver) fprintlnNonEmpty(output, " Storage Driver:", info.Driver)
if info.DriverStatus != nil { if info.DriverStatus != nil {
for _, pair := range info.DriverStatus { for _, pair := range info.DriverStatus {
fmt.Fprintf(output, " %s: %s\n", pair[0], pair[1]) fprintf(output, " %s: %s\n", pair[0], pair[1])
} }
} }
if info.SystemStatus != nil { if info.SystemStatus != nil {
for _, pair := range info.SystemStatus { for _, pair := range info.SystemStatus {
fmt.Fprintf(output, " %s: %s\n", pair[0], pair[1]) fprintf(output, " %s: %s\n", pair[0], pair[1])
} }
} }
fprintlnNonEmpty(output, " Logging Driver:", info.LoggingDriver) fprintlnNonEmpty(output, " Logging Driver:", info.LoggingDriver)
fprintlnNonEmpty(output, " Cgroup Driver:", info.CgroupDriver) fprintlnNonEmpty(output, " Cgroup Driver:", info.CgroupDriver)
fprintlnNonEmpty(output, " Cgroup Version:", info.CgroupVersion) fprintlnNonEmpty(output, " Cgroup Version:", info.CgroupVersion)
fmt.Fprintln(output, " Plugins:") fprintln(output, " Plugins:")
fmt.Fprintln(output, " Volume:", strings.Join(info.Plugins.Volume, " ")) fprintln(output, " Volume:", strings.Join(info.Plugins.Volume, " "))
fmt.Fprintln(output, " Network:", strings.Join(info.Plugins.Network, " ")) fprintln(output, " Network:", strings.Join(info.Plugins.Network, " "))
if len(info.Plugins.Authorization) != 0 { if len(info.Plugins.Authorization) != 0 {
fmt.Fprintln(output, " Authorization:", strings.Join(info.Plugins.Authorization, " ")) fprintln(output, " Authorization:", strings.Join(info.Plugins.Authorization, " "))
} }
fmt.Fprintln(output, " Log:", strings.Join(info.Plugins.Log, " ")) fprintln(output, " Log:", strings.Join(info.Plugins.Log, " "))
fmt.Fprintln(output, " Swarm:", info.Swarm.LocalNodeState) fprintln(output, " Swarm:", info.Swarm.LocalNodeState)
printSwarmInfo(output, *info.Info) printSwarmInfo(output, *info.Info)
if len(info.Runtimes) > 0 { if len(info.Runtimes) > 0 {
@ -259,12 +258,12 @@ func prettyPrintServerInfo(streams command.Streams, info *info) []error {
for name := range info.Runtimes { for name := range info.Runtimes {
names = append(names, name) names = append(names, name)
} }
fmt.Fprintln(output, " Runtimes:", strings.Join(names, " ")) fprintln(output, " Runtimes:", strings.Join(names, " "))
fmt.Fprintln(output, " Default Runtime:", info.DefaultRuntime) fprintln(output, " Default Runtime:", info.DefaultRuntime)
} }
if info.OSType == "linux" { if info.OSType == "linux" {
fmt.Fprintln(output, " Init Binary:", info.InitBinary) fprintln(output, " Init Binary:", info.InitBinary)
for _, ci := range []struct { for _, ci := range []struct {
Name string Name string
@ -274,23 +273,23 @@ func prettyPrintServerInfo(streams command.Streams, info *info) []error {
{"runc", info.RuncCommit}, {"runc", info.RuncCommit},
{"init", info.InitCommit}, {"init", info.InitCommit},
} { } {
fmt.Fprintf(output, " %s version: %s", ci.Name, ci.Commit.ID) fprintf(output, " %s version: %s", ci.Name, ci.Commit.ID)
if ci.Commit.ID != ci.Commit.Expected { if ci.Commit.ID != ci.Commit.Expected {
fmt.Fprintf(output, " (expected: %s)", ci.Commit.Expected) fprintf(output, " (expected: %s)", ci.Commit.Expected)
} }
fmt.Fprint(output, "\n") fprintln(output)
} }
if len(info.SecurityOptions) != 0 { if len(info.SecurityOptions) != 0 {
if kvs, err := types.DecodeSecurityOptions(info.SecurityOptions); err != nil { if kvs, err := types.DecodeSecurityOptions(info.SecurityOptions); err != nil {
errs = append(errs, err) errs = append(errs, err)
} else { } else {
fmt.Fprintln(output, " Security Options:") fprintln(output, " Security Options:")
for _, so := range kvs { for _, so := range kvs {
fmt.Fprintln(output, " "+so.Name) fprintln(output, " "+so.Name)
for _, o := range so.Options { for _, o := range so.Options {
switch o.Key { switch o.Key {
case "profile": case "profile":
fmt.Fprintln(output, " Profile:", o.Value) fprintln(output, " Profile:", o.Value)
} }
} }
} }
@ -300,25 +299,25 @@ func prettyPrintServerInfo(streams command.Streams, info *info) []error {
// Isolation only has meaning on a Windows daemon. // Isolation only has meaning on a Windows daemon.
if info.OSType == "windows" { if info.OSType == "windows" {
fmt.Fprintln(output, " Default Isolation:", info.Isolation) fprintln(output, " Default Isolation:", info.Isolation)
} }
fprintlnNonEmpty(output, " Kernel Version:", info.KernelVersion) fprintlnNonEmpty(output, " Kernel Version:", info.KernelVersion)
fprintlnNonEmpty(output, " Operating System:", info.OperatingSystem) fprintlnNonEmpty(output, " Operating System:", info.OperatingSystem)
fprintlnNonEmpty(output, " OSType:", info.OSType) fprintlnNonEmpty(output, " OSType:", info.OSType)
fprintlnNonEmpty(output, " Architecture:", info.Architecture) fprintlnNonEmpty(output, " Architecture:", info.Architecture)
fmt.Fprintln(output, " CPUs:", info.NCPU) fprintln(output, " CPUs:", info.NCPU)
fmt.Fprintln(output, " Total Memory:", units.BytesSize(float64(info.MemTotal))) fprintln(output, " Total Memory:", units.BytesSize(float64(info.MemTotal)))
fprintlnNonEmpty(output, " Name:", info.Name) fprintlnNonEmpty(output, " Name:", info.Name)
fprintlnNonEmpty(output, " ID:", info.ID) fprintlnNonEmpty(output, " ID:", info.ID)
fmt.Fprintln(output, " Docker Root Dir:", info.DockerRootDir) fprintln(output, " Docker Root Dir:", info.DockerRootDir)
fmt.Fprintln(output, " Debug Mode:", info.Debug) fprintln(output, " Debug Mode:", info.Debug)
if info.Debug { if info.Debug {
fmt.Fprintln(output, " File Descriptors:", info.NFd) fprintln(output, " File Descriptors:", info.NFd)
fmt.Fprintln(output, " Goroutines:", info.NGoroutines) fprintln(output, " Goroutines:", info.NGoroutines)
fmt.Fprintln(output, " System Time:", info.SystemTime) fprintln(output, " System Time:", info.SystemTime)
fmt.Fprintln(output, " EventsListeners:", info.NEventsListener) fprintln(output, " EventsListeners:", info.NEventsListener)
} }
fprintlnNonEmpty(output, " HTTP Proxy:", info.HTTPProxy) fprintlnNonEmpty(output, " HTTP Proxy:", info.HTTPProxy)
@ -326,48 +325,48 @@ func prettyPrintServerInfo(streams command.Streams, info *info) []error {
fprintlnNonEmpty(output, " No Proxy:", info.NoProxy) fprintlnNonEmpty(output, " No Proxy:", info.NoProxy)
fprintlnNonEmpty(output, " Username:", info.UserName) fprintlnNonEmpty(output, " Username:", info.UserName)
if len(info.Labels) > 0 { if len(info.Labels) > 0 {
fmt.Fprintln(output, " Labels:") fprintln(output, " Labels:")
for _, lbl := range info.Labels { for _, lbl := range info.Labels {
fmt.Fprintln(output, " "+lbl) fprintln(output, " "+lbl)
} }
} }
fmt.Fprintln(output, " Experimental:", info.ExperimentalBuild) fprintln(output, " Experimental:", info.ExperimentalBuild)
if info.RegistryConfig != nil && (len(info.RegistryConfig.InsecureRegistryCIDRs) > 0 || len(info.RegistryConfig.IndexConfigs) > 0) { if info.RegistryConfig != nil && (len(info.RegistryConfig.InsecureRegistryCIDRs) > 0 || len(info.RegistryConfig.IndexConfigs) > 0) {
fmt.Fprintln(output, " Insecure Registries:") fprintln(output, " Insecure Registries:")
for _, reg := range info.RegistryConfig.IndexConfigs { for _, registryConfig := range info.RegistryConfig.IndexConfigs {
if !reg.Secure { if !registryConfig.Secure {
fmt.Fprintln(output, " "+reg.Name) fprintln(output, " "+registryConfig.Name)
} }
} }
for _, reg := range info.RegistryConfig.InsecureRegistryCIDRs { for _, registryConfig := range info.RegistryConfig.InsecureRegistryCIDRs {
mask, _ := reg.Mask.Size() mask, _ := registryConfig.Mask.Size()
fmt.Fprintf(output, " %s/%d\n", reg.IP.String(), mask) fprintf(output, " %s/%d\n", registryConfig.IP.String(), mask)
} }
} }
if info.RegistryConfig != nil && len(info.RegistryConfig.Mirrors) > 0 { if info.RegistryConfig != nil && len(info.RegistryConfig.Mirrors) > 0 {
fmt.Fprintln(output, " Registry Mirrors:") fprintln(output, " Registry Mirrors:")
for _, mirror := range info.RegistryConfig.Mirrors { for _, mirror := range info.RegistryConfig.Mirrors {
fmt.Fprintln(output, " "+mirror) fprintln(output, " "+mirror)
} }
} }
fmt.Fprintln(output, " Live Restore Enabled:", info.LiveRestoreEnabled) fprintln(output, " Live Restore Enabled:", info.LiveRestoreEnabled)
if info.ProductLicense != "" { if info.ProductLicense != "" {
fmt.Fprintln(output, " Product License:", info.ProductLicense) fprintln(output, " Product License:", info.ProductLicense)
} }
if info.DefaultAddressPools != nil && len(info.DefaultAddressPools) > 0 { if info.DefaultAddressPools != nil && len(info.DefaultAddressPools) > 0 {
fmt.Fprintln(output, " Default Address Pools:") fprintln(output, " Default Address Pools:")
for _, pool := range info.DefaultAddressPools { for _, pool := range info.DefaultAddressPools {
fmt.Fprintf(output, " Base: %s, Size: %d\n", pool.Base, pool.Size) fprintf(output, " Base: %s, Size: %d\n", pool.Base, pool.Size)
} }
} }
fmt.Fprint(output, "\n") fprintln(output)
printServerWarnings(streams.Err(), info) printServerWarnings(streams.Err(), info)
return errs return errs
} }
@ -377,67 +376,67 @@ func printSwarmInfo(output io.Writer, info types.Info) {
if info.Swarm.LocalNodeState == swarm.LocalNodeStateInactive || info.Swarm.LocalNodeState == swarm.LocalNodeStateLocked { if info.Swarm.LocalNodeState == swarm.LocalNodeStateInactive || info.Swarm.LocalNodeState == swarm.LocalNodeStateLocked {
return return
} }
fmt.Fprintln(output, " NodeID:", info.Swarm.NodeID) fprintln(output, " NodeID:", info.Swarm.NodeID)
if info.Swarm.Error != "" { if info.Swarm.Error != "" {
fmt.Fprintln(output, " Error:", info.Swarm.Error) fprintln(output, " Error:", info.Swarm.Error)
} }
fmt.Fprintln(output, " Is Manager:", info.Swarm.ControlAvailable) fprintln(output, " Is Manager:", info.Swarm.ControlAvailable)
if info.Swarm.Cluster != nil && info.Swarm.ControlAvailable && info.Swarm.Error == "" && info.Swarm.LocalNodeState != swarm.LocalNodeStateError { if info.Swarm.Cluster != nil && info.Swarm.ControlAvailable && info.Swarm.Error == "" && info.Swarm.LocalNodeState != swarm.LocalNodeStateError {
fmt.Fprintln(output, " ClusterID:", info.Swarm.Cluster.ID) fprintln(output, " ClusterID:", info.Swarm.Cluster.ID)
fmt.Fprintln(output, " Managers:", info.Swarm.Managers) fprintln(output, " Managers:", info.Swarm.Managers)
fmt.Fprintln(output, " Nodes:", info.Swarm.Nodes) fprintln(output, " Nodes:", info.Swarm.Nodes)
var strAddrPool strings.Builder var strAddrPool strings.Builder
if info.Swarm.Cluster.DefaultAddrPool != nil { if info.Swarm.Cluster.DefaultAddrPool != nil {
for _, p := range info.Swarm.Cluster.DefaultAddrPool { for _, p := range info.Swarm.Cluster.DefaultAddrPool {
strAddrPool.WriteString(p + " ") strAddrPool.WriteString(p + " ")
} }
fmt.Fprintln(output, " Default Address Pool:", strAddrPool.String()) fprintln(output, " Default Address Pool:", strAddrPool.String())
fmt.Fprintln(output, " SubnetSize:", info.Swarm.Cluster.SubnetSize) fprintln(output, " SubnetSize:", info.Swarm.Cluster.SubnetSize)
} }
if info.Swarm.Cluster.DataPathPort > 0 { if info.Swarm.Cluster.DataPathPort > 0 {
fmt.Fprintln(output, " Data Path Port:", info.Swarm.Cluster.DataPathPort) fprintln(output, " Data Path Port:", info.Swarm.Cluster.DataPathPort)
} }
fmt.Fprintln(output, " Orchestration:") fprintln(output, " Orchestration:")
taskHistoryRetentionLimit := int64(0) taskHistoryRetentionLimit := int64(0)
if info.Swarm.Cluster.Spec.Orchestration.TaskHistoryRetentionLimit != nil { if info.Swarm.Cluster.Spec.Orchestration.TaskHistoryRetentionLimit != nil {
taskHistoryRetentionLimit = *info.Swarm.Cluster.Spec.Orchestration.TaskHistoryRetentionLimit taskHistoryRetentionLimit = *info.Swarm.Cluster.Spec.Orchestration.TaskHistoryRetentionLimit
} }
fmt.Fprintln(output, " Task History Retention Limit:", taskHistoryRetentionLimit) fprintln(output, " Task History Retention Limit:", taskHistoryRetentionLimit)
fmt.Fprintln(output, " Raft:") fprintln(output, " Raft:")
fmt.Fprintln(output, " Snapshot Interval:", info.Swarm.Cluster.Spec.Raft.SnapshotInterval) fprintln(output, " Snapshot Interval:", info.Swarm.Cluster.Spec.Raft.SnapshotInterval)
if info.Swarm.Cluster.Spec.Raft.KeepOldSnapshots != nil { if info.Swarm.Cluster.Spec.Raft.KeepOldSnapshots != nil {
fmt.Fprintf(output, " Number of Old Snapshots to Retain: %d\n", *info.Swarm.Cluster.Spec.Raft.KeepOldSnapshots) fprintf(output, " Number of Old Snapshots to Retain: %d\n", *info.Swarm.Cluster.Spec.Raft.KeepOldSnapshots)
} }
fmt.Fprintln(output, " Heartbeat Tick:", info.Swarm.Cluster.Spec.Raft.HeartbeatTick) fprintln(output, " Heartbeat Tick:", info.Swarm.Cluster.Spec.Raft.HeartbeatTick)
fmt.Fprintln(output, " Election Tick:", info.Swarm.Cluster.Spec.Raft.ElectionTick) fprintln(output, " Election Tick:", info.Swarm.Cluster.Spec.Raft.ElectionTick)
fmt.Fprintln(output, " Dispatcher:") fprintln(output, " Dispatcher:")
fmt.Fprintln(output, " Heartbeat Period:", units.HumanDuration(info.Swarm.Cluster.Spec.Dispatcher.HeartbeatPeriod)) fprintln(output, " Heartbeat Period:", units.HumanDuration(info.Swarm.Cluster.Spec.Dispatcher.HeartbeatPeriod))
fmt.Fprintln(output, " CA Configuration:") fprintln(output, " CA Configuration:")
fmt.Fprintln(output, " Expiry Duration:", units.HumanDuration(info.Swarm.Cluster.Spec.CAConfig.NodeCertExpiry)) fprintln(output, " Expiry Duration:", units.HumanDuration(info.Swarm.Cluster.Spec.CAConfig.NodeCertExpiry))
fmt.Fprintln(output, " Force Rotate:", info.Swarm.Cluster.Spec.CAConfig.ForceRotate) fprintln(output, " Force Rotate:", info.Swarm.Cluster.Spec.CAConfig.ForceRotate)
if caCert := strings.TrimSpace(info.Swarm.Cluster.Spec.CAConfig.SigningCACert); caCert != "" { if caCert := strings.TrimSpace(info.Swarm.Cluster.Spec.CAConfig.SigningCACert); caCert != "" {
fmt.Fprintf(output, " Signing CA Certificate: \n%s\n\n", caCert) fprintf(output, " Signing CA Certificate: \n%s\n\n", caCert)
} }
if len(info.Swarm.Cluster.Spec.CAConfig.ExternalCAs) > 0 { if len(info.Swarm.Cluster.Spec.CAConfig.ExternalCAs) > 0 {
fmt.Fprintln(output, " External CAs:") fprintln(output, " External CAs:")
for _, entry := range info.Swarm.Cluster.Spec.CAConfig.ExternalCAs { for _, entry := range info.Swarm.Cluster.Spec.CAConfig.ExternalCAs {
fmt.Fprintf(output, " %s: %s\n", entry.Protocol, entry.URL) fprintf(output, " %s: %s\n", entry.Protocol, entry.URL)
} }
} }
fmt.Fprintln(output, " Autolock Managers:", info.Swarm.Cluster.Spec.EncryptionConfig.AutoLockManagers) fprintln(output, " Autolock Managers:", info.Swarm.Cluster.Spec.EncryptionConfig.AutoLockManagers)
fmt.Fprintln(output, " Root Rotation In Progress:", info.Swarm.Cluster.RootRotationInProgress) fprintln(output, " Root Rotation In Progress:", info.Swarm.Cluster.RootRotationInProgress)
} }
fmt.Fprintln(output, " Node Address:", info.Swarm.NodeAddr) fprintln(output, " Node Address:", info.Swarm.NodeAddr)
if len(info.Swarm.RemoteManagers) > 0 { if len(info.Swarm.RemoteManagers) > 0 {
managers := []string{} managers := []string{}
for _, entry := range info.Swarm.RemoteManagers { for _, entry := range info.Swarm.RemoteManagers {
managers = append(managers, entry.Addr) managers = append(managers, entry.Addr)
} }
sort.Strings(managers) sort.Strings(managers)
fmt.Fprintln(output, " Manager Addresses:") fprintln(output, " Manager Addresses:")
for _, entry := range managers { for _, entry := range managers {
fmt.Fprintf(output, " %s\n", entry) fprintf(output, " %s\n", entry)
} }
} }
} }
@ -447,7 +446,7 @@ func printServerWarnings(stdErr io.Writer, info *info) {
printSecurityOptionsWarnings(stdErr, *info.Info) printSecurityOptionsWarnings(stdErr, *info.Info)
} }
if len(info.Warnings) > 0 { if len(info.Warnings) > 0 {
fmt.Fprintln(stdErr, strings.Join(info.Warnings, "\n")) fprintln(stdErr, strings.Join(info.Warnings, "\n"))
return return
} }
// daemon didn't return warnings. Fallback to old behavior // daemon didn't return warnings. Fallback to old behavior
@ -487,38 +486,38 @@ func printServerWarningsLegacy(stdErr io.Writer, info types.Info) {
return return
} }
if !info.MemoryLimit { if !info.MemoryLimit {
fmt.Fprintln(stdErr, "WARNING: No memory limit support") fprintln(stdErr, "WARNING: No memory limit support")
} }
if !info.SwapLimit { if !info.SwapLimit {
fmt.Fprintln(stdErr, "WARNING: No swap limit support") fprintln(stdErr, "WARNING: No swap limit support")
} }
if !info.OomKillDisable && info.CgroupVersion != "2" { if !info.OomKillDisable && info.CgroupVersion != "2" {
fmt.Fprintln(stdErr, "WARNING: No oom kill disable support") fprintln(stdErr, "WARNING: No oom kill disable support")
} }
if !info.CPUCfsQuota { if !info.CPUCfsQuota {
fmt.Fprintln(stdErr, "WARNING: No cpu cfs quota support") fprintln(stdErr, "WARNING: No cpu cfs quota support")
} }
if !info.CPUCfsPeriod { if !info.CPUCfsPeriod {
fmt.Fprintln(stdErr, "WARNING: No cpu cfs period support") fprintln(stdErr, "WARNING: No cpu cfs period support")
} }
if !info.CPUShares { if !info.CPUShares {
fmt.Fprintln(stdErr, "WARNING: No cpu shares support") fprintln(stdErr, "WARNING: No cpu shares support")
} }
if !info.CPUSet { if !info.CPUSet {
fmt.Fprintln(stdErr, "WARNING: No cpuset support") fprintln(stdErr, "WARNING: No cpuset support")
} }
if !info.IPv4Forwarding { if !info.IPv4Forwarding {
fmt.Fprintln(stdErr, "WARNING: IPv4 forwarding is disabled") fprintln(stdErr, "WARNING: IPv4 forwarding is disabled")
} }
if !info.BridgeNfIptables { if !info.BridgeNfIptables {
fmt.Fprintln(stdErr, "WARNING: bridge-nf-call-iptables is disabled") fprintln(stdErr, "WARNING: bridge-nf-call-iptables is disabled")
} }
if !info.BridgeNfIP6tables { if !info.BridgeNfIP6tables {
fmt.Fprintln(stdErr, "WARNING: bridge-nf-call-ip6tables is disabled") fprintln(stdErr, "WARNING: bridge-nf-call-ip6tables is disabled")
} }
} }
func formatInfo(dockerCli command.Cli, info info, format string) error { func formatInfo(output io.Writer, info info, format string) error {
if format == formatter.JSONFormatKey { if format == formatter.JSONFormatKey {
format = formatter.JSONFormat format = formatter.JSONFormat
} }
@ -535,13 +534,21 @@ func formatInfo(dockerCli command.Cli, info info, format string) error {
Status: "template parsing error: " + err.Error(), Status: "template parsing error: " + err.Error(),
} }
} }
err = tmpl.Execute(dockerCli.Out(), info) err = tmpl.Execute(output, info)
dockerCli.Out().Write([]byte{'\n'}) fprintln(output)
return err return err
} }
func fprintf(w io.Writer, format string, a ...any) {
_, _ = fmt.Fprintf(w, format, a...)
}
func fprintln(w io.Writer, a ...any) {
_, _ = fmt.Fprintln(w, a...)
}
func fprintlnNonEmpty(w io.Writer, label, value string) { func fprintlnNonEmpty(w io.Writer, label, value string) {
if value != "" { if value != "" {
fmt.Fprintln(w, label, value) _, _ = fmt.Fprintln(w, label, value)
} }
} }

View File

@ -406,12 +406,12 @@ func TestPrettyPrintInfo(t *testing.T) {
if tc.jsonGolden != "" { if tc.jsonGolden != "" {
cli = test.NewFakeCli(&fakeClient{}) cli = test.NewFakeCli(&fakeClient{})
assert.NilError(t, formatInfo(cli, tc.dockerInfo, "{{json .}}")) assert.NilError(t, formatInfo(cli.Out(), tc.dockerInfo, "{{json .}}"))
golden.Assert(t, cli.OutBuffer().String(), tc.jsonGolden+".json.golden") golden.Assert(t, cli.OutBuffer().String(), tc.jsonGolden+".json.golden")
assert.Check(t, is.Equal("", cli.ErrBuffer().String())) assert.Check(t, is.Equal("", cli.ErrBuffer().String()))
cli = test.NewFakeCli(&fakeClient{}) cli = test.NewFakeCli(&fakeClient{})
assert.NilError(t, formatInfo(cli, tc.dockerInfo, "json")) assert.NilError(t, formatInfo(cli.Out(), tc.dockerInfo, "json"))
golden.Assert(t, cli.OutBuffer().String(), tc.jsonGolden+".json.golden") golden.Assert(t, cli.OutBuffer().String(), tc.jsonGolden+".json.golden")
assert.Check(t, is.Equal("", cli.ErrBuffer().String())) assert.Check(t, is.Equal("", cli.ErrBuffer().String()))
} }
@ -473,7 +473,7 @@ func TestFormatInfo(t *testing.T) {
Info: &sampleInfoNoSwarm, Info: &sampleInfoNoSwarm,
ClientInfo: &clientInfo{Debug: true}, ClientInfo: &clientInfo{Debug: true},
} }
err := formatInfo(cli, info, tc.template) err := formatInfo(cli.Out(), info, tc.template)
if tc.expectedOut != "" { if tc.expectedOut != "" {
assert.NilError(t, err) assert.NilError(t, err)
assert.Equal(t, cli.OutBuffer().String(), tc.expectedOut) assert.Equal(t, cli.OutBuffer().String(), tc.expectedOut)