github.com/panekj/cli@v0.0.0-20230304125325-467dd2f3797e/cli/command/system/info.go (about)

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