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