github.com/khulnasoft/cli@v0.0.0-20240402070845-01bcad7beefa/cli/command/system/info_test.go (about)

     1  package system
     2  
     3  import (
     4  	"encoding/base64"
     5  	"net"
     6  	"testing"
     7  	"time"
     8  
     9  	registrytypes "github.com/docker/docker/api/types/registry"
    10  	"github.com/docker/docker/api/types/swarm"
    11  	"github.com/docker/docker/api/types/system"
    12  	pluginmanager "github.com/khulnasoft/cli/cli-plugins/manager"
    13  	"github.com/khulnasoft/cli/internal/test"
    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 = system.Info{
    28  	ID:                sampleID,
    29  	Containers:        0,
    30  	ContainersRunning: 0,
    31  	ContainersPaused:  0,
    32  	ContainersStopped: 0,
    33  	Images:            0,
    34  	Driver:            "overlay2",
    35  	DriverStatus: [][2]string{
    36  		{"Backing Filesystem", "extfs"},
    37  		{"Supports d_type", "true"},
    38  		{"Using metacopy", "false"},
    39  		{"Native Overlay Diff", "true"},
    40  	},
    41  	SystemStatus: nil,
    42  	Plugins: system.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", "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: &registrytypes.ServiceConfig{
    73  		AllowNondistributableArtifactsCIDRs:     nil,
    74  		AllowNondistributableArtifactsHostnames: nil,
    75  		InsecureRegistryCIDRs: []*registrytypes.NetIPNet{
    76  			{
    77  				IP:   net.ParseIP("127.0.0.0"),
    78  				Mask: net.IPv4Mask(255, 0, 0, 0),
    79  			},
    80  		},
    81  		IndexConfigs: map[string]*registrytypes.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  	Runtimes: map[string]system.RuntimeWithStatus{
   102  		"runc": {
   103  			Runtime: system.Runtime{
   104  				Path: "docker-runc",
   105  				Args: nil,
   106  			},
   107  		},
   108  	},
   109  	DefaultRuntime:     "runc",
   110  	Swarm:              swarm.Info{LocalNodeState: "inactive"},
   111  	LiveRestoreEnabled: false,
   112  	Isolation:          "",
   113  	InitBinary:         "docker-init",
   114  	ContainerdCommit: system.Commit{
   115  		ID:       "6e23458c129b551d5c9871e5174f6b1b7f6d1170",
   116  		Expected: "6e23458c129b551d5c9871e5174f6b1b7f6d1170",
   117  	},
   118  	RuncCommit: system.Commit{
   119  		ID:       "810190ceaa507aa2727d7ae6f4790c76ec150bd2",
   120  		Expected: "810190ceaa507aa2727d7ae6f4790c76ec150bd2",
   121  	},
   122  	InitCommit: system.Commit{
   123  		ID:       "949e6fa",
   124  		Expected: "949e6fa",
   125  	},
   126  	SecurityOptions: []string{"name=apparmor", "name=seccomp,profile=default"},
   127  	DefaultAddressPools: []system.NetworkAddressPool{
   128  		{
   129  			Base: "10.123.0.0/16",
   130  			Size: 24,
   131  		},
   132  	},
   133  	CDISpecDirs: []string{"/etc/cdi", "/var/run/cdi"},
   134  }
   135  
   136  var sampleSwarmInfo = swarm.Info{
   137  	NodeID:           "qo2dfdig9mmxqkawulggepdih",
   138  	NodeAddr:         "165.227.107.89",
   139  	LocalNodeState:   "active",
   140  	ControlAvailable: true,
   141  	Error:            "",
   142  	RemoteManagers: []swarm.Peer{
   143  		{
   144  			NodeID: "qo2dfdig9mmxqkawulggepdih",
   145  			Addr:   "165.227.107.89:2377",
   146  		},
   147  	},
   148  	Nodes:    1,
   149  	Managers: 1,
   150  	Cluster: &swarm.ClusterInfo{
   151  		ID: "9vs5ygs0gguyyec4iqf2314c0",
   152  		Meta: swarm.Meta{
   153  			Version:   swarm.Version{Index: 11},
   154  			CreatedAt: time.Date(2017, 8, 24, 17, 34, 19, 278062352, time.UTC),
   155  			UpdatedAt: time.Date(2017, 8, 24, 17, 34, 42, 398815481, time.UTC),
   156  		},
   157  		Spec: swarm.Spec{
   158  			Annotations: swarm.Annotations{
   159  				Name:   "default",
   160  				Labels: nil,
   161  			},
   162  			Orchestration: swarm.OrchestrationConfig{
   163  				TaskHistoryRetentionLimit: &[]int64{5}[0],
   164  			},
   165  			Raft: swarm.RaftConfig{
   166  				SnapshotInterval:           10000,
   167  				KeepOldSnapshots:           &[]uint64{0}[0],
   168  				LogEntriesForSlowFollowers: 500,
   169  				ElectionTick:               3,
   170  				HeartbeatTick:              1,
   171  			},
   172  			Dispatcher: swarm.DispatcherConfig{
   173  				HeartbeatPeriod: 5000000000,
   174  			},
   175  			CAConfig: swarm.CAConfig{
   176  				NodeCertExpiry: 7776000000000000,
   177  			},
   178  			TaskDefaults: swarm.TaskDefaults{},
   179  			EncryptionConfig: swarm.EncryptionConfig{
   180  				AutoLockManagers: true,
   181  			},
   182  		},
   183  		TLSInfo: swarm.TLSInfo{
   184  			TrustRoot: `
   185  -----BEGIN CERTIFICATE-----
   186  MIIBajCCARCgAwIBAgIUaFCW5xsq8eyiJ+Pmcv3MCflMLnMwCgYIKoZIzj0EAwIw
   187  EzERMA8GA1UEAxMIc3dhcm0tY2EwHhcNMTcwODI0MTcyOTAwWhcNMzcwODE5MTcy
   188  OTAwWjATMREwDwYDVQQDEwhzd2FybS1jYTBZMBMGByqGSM49AgEGCCqGSM49AwEH
   189  A0IABDy7NebyUJyUjWJDBUdnZoV6GBxEGKO4TZPNDwnxDxJcUdLVaB7WGa4/DLrW
   190  UfsVgh1JGik2VTiLuTMA1tLlNPOjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB
   191  Af8EBTADAQH/MB0GA1UdDgQWBBQl16XFtaaXiUAwEuJptJlDjfKskDAKBggqhkjO
   192  PQQDAgNIADBFAiEAo9fTQNM5DP9bHVcTJYfl2Cay1bFu1E+lnpmN+EYJfeACIGKH
   193  1pCUkZ+D0IB6CiEZGWSHyLuXPM1rlP+I5KuS7sB8
   194  -----END CERTIFICATE-----
   195  `,
   196  			CertIssuerSubject: base64Decode("MBMxETAPBgNVBAMTCHN3YXJtLWNh"),
   197  			CertIssuerPublicKey: base64Decode(
   198  				"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPLs15vJQnJSNYkMFR2dmhXoYHEQYo7hNk80PCfEPElxR0tVoHtYZrj8MutZR+xWCHUkaKTZVOIu5MwDW0uU08w=="),
   199  		},
   200  		RootRotationInProgress: false,
   201  	},
   202  }
   203  
   204  var samplePluginsInfo = []pluginmanager.Plugin{
   205  	{
   206  		Name: "goodplugin",
   207  		Path: "/path/to/docker-goodplugin",
   208  		Metadata: pluginmanager.Metadata{
   209  			SchemaVersion:    "0.1.0",
   210  			ShortDescription: "unit test is good",
   211  			Vendor:           "ACME Corp",
   212  			Version:          "0.1.0",
   213  		},
   214  	},
   215  	{
   216  		Name: "unversionedplugin",
   217  		Path: "/path/to/docker-unversionedplugin",
   218  		Metadata: pluginmanager.Metadata{
   219  			SchemaVersion:    "0.1.0",
   220  			ShortDescription: "this plugin has no version",
   221  			Vendor:           "ACME Corp",
   222  		},
   223  	},
   224  	{
   225  		Name: "badplugin",
   226  		Path: "/path/to/docker-badplugin",
   227  		Err:  pluginmanager.NewPluginError("something wrong"),
   228  	},
   229  }
   230  
   231  func TestPrettyPrintInfo(t *testing.T) {
   232  	infoWithSwarm := sampleInfoNoSwarm
   233  	infoWithSwarm.Swarm = sampleSwarmInfo
   234  
   235  	infoWithWarningsLinux := sampleInfoNoSwarm
   236  	infoWithWarningsLinux.MemoryLimit = false
   237  	infoWithWarningsLinux.SwapLimit = false
   238  	infoWithWarningsLinux.KernelMemory = false
   239  	infoWithWarningsLinux.OomKillDisable = false
   240  	infoWithWarningsLinux.CPUCfsQuota = false
   241  	infoWithWarningsLinux.CPUCfsPeriod = false
   242  	infoWithWarningsLinux.CPUShares = false
   243  	infoWithWarningsLinux.CPUSet = false
   244  	infoWithWarningsLinux.IPv4Forwarding = false
   245  	infoWithWarningsLinux.BridgeNfIptables = false
   246  	infoWithWarningsLinux.BridgeNfIP6tables = false
   247  
   248  	sampleInfoDaemonWarnings := sampleInfoNoSwarm
   249  	sampleInfoDaemonWarnings.Warnings = []string{
   250  		"WARNING: No memory limit support",
   251  		"WARNING: No swap 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  	sampleInfoLabelsNil := sampleInfoNoSwarm
   266  	sampleInfoLabelsNil.Labels = nil
   267  	sampleInfoLabelsEmpty := sampleInfoNoSwarm
   268  	sampleInfoLabelsEmpty.Labels = []string{}
   269  
   270  	for _, tc := range []struct {
   271  		doc        string
   272  		dockerInfo dockerInfo
   273  
   274  		prettyGolden   string
   275  		warningsGolden string
   276  		jsonGolden     string
   277  		expectedError  string
   278  	}{
   279  		{
   280  			doc: "info without swarm",
   281  			dockerInfo: dockerInfo{
   282  				Info: &sampleInfoNoSwarm,
   283  				ClientInfo: &clientInfo{
   284  					clientVersion: clientVersion{
   285  						Platform: &platformInfo{Name: "Docker Engine - Community"},
   286  						Version:  "24.0.0",
   287  						Context:  "default",
   288  					},
   289  					Debug: true,
   290  				},
   291  			},
   292  			prettyGolden: "docker-info-no-swarm",
   293  			jsonGolden:   "docker-info-no-swarm",
   294  		},
   295  		{
   296  			doc: "info with plugins",
   297  			dockerInfo: dockerInfo{
   298  				Info: &sampleInfoNoSwarm,
   299  				ClientInfo: &clientInfo{
   300  					clientVersion: clientVersion{Context: "default"},
   301  					Plugins:       samplePluginsInfo,
   302  				},
   303  			},
   304  			prettyGolden:   "docker-info-plugins",
   305  			jsonGolden:     "docker-info-plugins",
   306  			warningsGolden: "docker-info-plugins-warnings",
   307  		},
   308  		{
   309  			doc: "info with nil labels",
   310  			dockerInfo: dockerInfo{
   311  				Info:       &sampleInfoLabelsNil,
   312  				ClientInfo: &clientInfo{clientVersion: clientVersion{Context: "default"}},
   313  			},
   314  			prettyGolden: "docker-info-with-labels-nil",
   315  		},
   316  		{
   317  			doc: "info with empty labels",
   318  			dockerInfo: dockerInfo{
   319  				Info:       &sampleInfoLabelsEmpty,
   320  				ClientInfo: &clientInfo{clientVersion: clientVersion{Context: "default"}},
   321  			},
   322  			prettyGolden: "docker-info-with-labels-empty",
   323  		},
   324  		{
   325  			doc: "info with swarm",
   326  			dockerInfo: dockerInfo{
   327  				Info: &infoWithSwarm,
   328  				ClientInfo: &clientInfo{
   329  					clientVersion: clientVersion{Context: "default"},
   330  					Debug:         false,
   331  				},
   332  			},
   333  			prettyGolden: "docker-info-with-swarm",
   334  			jsonGolden:   "docker-info-with-swarm",
   335  		},
   336  		{
   337  			doc: "info with daemon warnings",
   338  			dockerInfo: dockerInfo{
   339  				Info: &sampleInfoDaemonWarnings,
   340  				ClientInfo: &clientInfo{
   341  					clientVersion: clientVersion{
   342  						Platform: &platformInfo{Name: "Docker Engine - Community"},
   343  						Version:  "24.0.0",
   344  						Context:  "default",
   345  					},
   346  					Debug: true,
   347  				},
   348  			},
   349  			prettyGolden:   "docker-info-no-swarm",
   350  			warningsGolden: "docker-info-warnings",
   351  			jsonGolden:     "docker-info-daemon-warnings",
   352  		},
   353  		{
   354  			doc: "errors for both",
   355  			dockerInfo: dockerInfo{
   356  				ServerErrors: []string{"a server error occurred"},
   357  				ClientErrors: []string{"a client error occurred"},
   358  			},
   359  			prettyGolden:   "docker-info-errors",
   360  			jsonGolden:     "docker-info-errors",
   361  			warningsGolden: "docker-info-errors-stderr",
   362  			expectedError:  "errors pretty printing info",
   363  		},
   364  		{
   365  			doc: "bad security info",
   366  			dockerInfo: dockerInfo{
   367  				Info:         &sampleInfoBadSecurity,
   368  				ServerErrors: []string{"a server error occurred"},
   369  				ClientInfo:   &clientInfo{Debug: false},
   370  			},
   371  			prettyGolden:   "docker-info-badsec",
   372  			jsonGolden:     "docker-info-badsec",
   373  			warningsGolden: "docker-info-badsec-stderr",
   374  			expectedError:  "errors pretty printing info",
   375  		},
   376  	} {
   377  		tc := tc
   378  		t.Run(tc.doc, func(t *testing.T) {
   379  			cli := test.NewFakeCli(&fakeClient{})
   380  			err := prettyPrintInfo(cli, tc.dockerInfo)
   381  			if tc.expectedError == "" {
   382  				assert.NilError(t, err)
   383  			} else {
   384  				assert.Error(t, err, tc.expectedError)
   385  			}
   386  			golden.Assert(t, cli.OutBuffer().String(), tc.prettyGolden+".golden")
   387  			if tc.warningsGolden != "" {
   388  				golden.Assert(t, cli.ErrBuffer().String(), tc.warningsGolden+".golden")
   389  			} else {
   390  				assert.Check(t, is.Equal("", cli.ErrBuffer().String()))
   391  			}
   392  
   393  			if tc.jsonGolden != "" {
   394  				cli = test.NewFakeCli(&fakeClient{})
   395  				assert.NilError(t, formatInfo(cli.Out(), tc.dockerInfo, "{{json .}}"))
   396  				golden.Assert(t, cli.OutBuffer().String(), tc.jsonGolden+".json.golden")
   397  				assert.Check(t, is.Equal("", cli.ErrBuffer().String()))
   398  
   399  				cli = test.NewFakeCli(&fakeClient{})
   400  				assert.NilError(t, formatInfo(cli.Out(), tc.dockerInfo, "json"))
   401  				golden.Assert(t, cli.OutBuffer().String(), tc.jsonGolden+".json.golden")
   402  				assert.Check(t, is.Equal("", cli.ErrBuffer().String()))
   403  			}
   404  		})
   405  	}
   406  }
   407  
   408  func BenchmarkPrettyPrintInfo(b *testing.B) {
   409  	infoWithSwarm := sampleInfoNoSwarm
   410  	infoWithSwarm.Swarm = sampleSwarmInfo
   411  
   412  	info := dockerInfo{
   413  		Info: &infoWithSwarm,
   414  		ClientInfo: &clientInfo{
   415  			clientVersion: clientVersion{
   416  				Platform: &platformInfo{Name: "Docker Engine - Community"},
   417  				Version:  "24.0.0",
   418  				Context:  "default",
   419  			},
   420  			Debug: true,
   421  		},
   422  	}
   423  	cli := test.NewFakeCli(&fakeClient{})
   424  
   425  	b.ReportAllocs()
   426  	for i := 0; i < b.N; i++ {
   427  		_ = prettyPrintInfo(cli, info)
   428  		cli.ResetOutputBuffers()
   429  	}
   430  }
   431  
   432  func TestFormatInfo(t *testing.T) {
   433  	for _, tc := range []struct {
   434  		doc           string
   435  		template      string
   436  		expectedError string
   437  		expectedOut   string
   438  	}{
   439  		{
   440  			doc:         "basic",
   441  			template:    "{{.ID}}",
   442  			expectedOut: sampleID + "\n",
   443  		},
   444  		{
   445  			doc:           "syntax",
   446  			template:      "{{}",
   447  			expectedError: `Status: template parsing error: template: :1: unexpected "}" in command, Code: 64`,
   448  		},
   449  		{
   450  			doc:           "syntax",
   451  			template:      "{{.badString}}",
   452  			expectedError: `template: :1:2: executing "" at <.badString>: can't evaluate field badString in type system.dockerInfo`,
   453  		},
   454  	} {
   455  		tc := tc
   456  		t.Run(tc.doc, func(t *testing.T) {
   457  			cli := test.NewFakeCli(&fakeClient{})
   458  			info := dockerInfo{
   459  				Info:       &sampleInfoNoSwarm,
   460  				ClientInfo: &clientInfo{Debug: true},
   461  			}
   462  			err := formatInfo(cli.Out(), info, tc.template)
   463  			switch {
   464  			case tc.expectedOut != "":
   465  				assert.NilError(t, err)
   466  				assert.Equal(t, cli.OutBuffer().String(), tc.expectedOut)
   467  			case tc.expectedError != "":
   468  				assert.Error(t, err, tc.expectedError)
   469  			default:
   470  				t.Fatal("test expected to neither pass nor fail")
   471  			}
   472  		})
   473  	}
   474  }
   475  
   476  func TestNeedsServerInfo(t *testing.T) {
   477  	tests := []struct {
   478  		doc      string
   479  		template string
   480  		expected bool
   481  	}{
   482  		{
   483  			doc:      "no template",
   484  			template: "",
   485  			expected: true,
   486  		},
   487  		{
   488  			doc:      "JSON",
   489  			template: "json",
   490  			expected: true,
   491  		},
   492  		{
   493  			doc:      "JSON (all fields)",
   494  			template: "{{json .}}",
   495  			expected: true,
   496  		},
   497  		{
   498  			doc:      "JSON (Server ID)",
   499  			template: "{{json .ID}}",
   500  			expected: true,
   501  		},
   502  		{
   503  			doc:      "ClientInfo",
   504  			template: "{{json .ClientInfo}}",
   505  			expected: false,
   506  		},
   507  		{
   508  			doc:      "JSON ClientInfo",
   509  			template: "{{json .ClientInfo}}",
   510  			expected: false,
   511  		},
   512  		{
   513  			doc:      "JSON (Active context)",
   514  			template: "{{json .ClientInfo.Context}}",
   515  			expected: false,
   516  		},
   517  	}
   518  
   519  	inf := dockerInfo{ClientInfo: &clientInfo{}}
   520  	for _, tc := range tests {
   521  		tc := tc
   522  		t.Run(tc.doc, func(t *testing.T) {
   523  			assert.Equal(t, needsServerInfo(tc.template, inf), tc.expected)
   524  		})
   525  	}
   526  }