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: &registry.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  }