github.com/itscaro/cli@v0.0.0-20190705081621-c9db0fe93829/cli/config/configfile/file_test.go (about)

     1  package configfile
     2  
     3  import (
     4  	"bytes"
     5  	"io/ioutil"
     6  	"os"
     7  	"testing"
     8  
     9  	"github.com/docker/cli/cli/config/credentials"
    10  	"github.com/docker/cli/cli/config/types"
    11  	"gotest.tools/assert"
    12  	is "gotest.tools/assert/cmp"
    13  	"gotest.tools/golden"
    14  )
    15  
    16  func TestEncodeAuth(t *testing.T) {
    17  	newAuthConfig := &types.AuthConfig{Username: "ken", Password: "test"}
    18  	authStr := encodeAuth(newAuthConfig)
    19  
    20  	expected := &types.AuthConfig{}
    21  	var err error
    22  	expected.Username, expected.Password, err = decodeAuth(authStr)
    23  	assert.NilError(t, err)
    24  	assert.Check(t, is.DeepEqual(expected, newAuthConfig))
    25  }
    26  
    27  func TestProxyConfig(t *testing.T) {
    28  	httpProxy := "http://proxy.mycorp.com:3128"
    29  	httpsProxy := "https://user:password@proxy.mycorp.com:3129"
    30  	ftpProxy := "http://ftpproxy.mycorp.com:21"
    31  	noProxy := "*.intra.mycorp.com"
    32  	defaultProxyConfig := ProxyConfig{
    33  		HTTPProxy:  httpProxy,
    34  		HTTPSProxy: httpsProxy,
    35  		FTPProxy:   ftpProxy,
    36  		NoProxy:    noProxy,
    37  	}
    38  
    39  	cfg := ConfigFile{
    40  		Proxies: map[string]ProxyConfig{
    41  			"default": defaultProxyConfig,
    42  		},
    43  	}
    44  
    45  	proxyConfig := cfg.ParseProxyConfig("/var/run/docker.sock", nil)
    46  	expected := map[string]*string{
    47  		"HTTP_PROXY":  &httpProxy,
    48  		"http_proxy":  &httpProxy,
    49  		"HTTPS_PROXY": &httpsProxy,
    50  		"https_proxy": &httpsProxy,
    51  		"FTP_PROXY":   &ftpProxy,
    52  		"ftp_proxy":   &ftpProxy,
    53  		"NO_PROXY":    &noProxy,
    54  		"no_proxy":    &noProxy,
    55  	}
    56  	assert.Check(t, is.DeepEqual(expected, proxyConfig))
    57  }
    58  
    59  func TestProxyConfigOverride(t *testing.T) {
    60  	httpProxy := "http://proxy.mycorp.com:3128"
    61  	overrideHTTPProxy := "http://proxy.example.com:3128"
    62  	overrideNoProxy := ""
    63  	httpsProxy := "https://user:password@proxy.mycorp.com:3129"
    64  	ftpProxy := "http://ftpproxy.mycorp.com:21"
    65  	noProxy := "*.intra.mycorp.com"
    66  	defaultProxyConfig := ProxyConfig{
    67  		HTTPProxy:  httpProxy,
    68  		HTTPSProxy: httpsProxy,
    69  		FTPProxy:   ftpProxy,
    70  		NoProxy:    noProxy,
    71  	}
    72  
    73  	cfg := ConfigFile{
    74  		Proxies: map[string]ProxyConfig{
    75  			"default": defaultProxyConfig,
    76  		},
    77  	}
    78  
    79  	clone := func(s string) *string {
    80  		s2 := s
    81  		return &s2
    82  	}
    83  
    84  	ropts := map[string]*string{
    85  		"HTTP_PROXY": clone(overrideHTTPProxy),
    86  		"NO_PROXY":   clone(overrideNoProxy),
    87  	}
    88  	proxyConfig := cfg.ParseProxyConfig("/var/run/docker.sock", ropts)
    89  	expected := map[string]*string{
    90  		"HTTP_PROXY":  &overrideHTTPProxy,
    91  		"http_proxy":  &httpProxy,
    92  		"HTTPS_PROXY": &httpsProxy,
    93  		"https_proxy": &httpsProxy,
    94  		"FTP_PROXY":   &ftpProxy,
    95  		"ftp_proxy":   &ftpProxy,
    96  		"NO_PROXY":    &overrideNoProxy,
    97  		"no_proxy":    &noProxy,
    98  	}
    99  	assert.Check(t, is.DeepEqual(expected, proxyConfig))
   100  }
   101  
   102  func TestProxyConfigPerHost(t *testing.T) {
   103  	httpProxy := "http://proxy.mycorp.com:3128"
   104  	httpsProxy := "https://user:password@proxy.mycorp.com:3129"
   105  	ftpProxy := "http://ftpproxy.mycorp.com:21"
   106  	noProxy := "*.intra.mycorp.com"
   107  
   108  	extHTTPProxy := "http://proxy.example.com:3128"
   109  	extHTTPSProxy := "https://user:password@proxy.example.com:3129"
   110  	extFTPProxy := "http://ftpproxy.example.com:21"
   111  	extNoProxy := "*.intra.example.com"
   112  
   113  	defaultProxyConfig := ProxyConfig{
   114  		HTTPProxy:  httpProxy,
   115  		HTTPSProxy: httpsProxy,
   116  		FTPProxy:   ftpProxy,
   117  		NoProxy:    noProxy,
   118  	}
   119  	externalProxyConfig := ProxyConfig{
   120  		HTTPProxy:  extHTTPProxy,
   121  		HTTPSProxy: extHTTPSProxy,
   122  		FTPProxy:   extFTPProxy,
   123  		NoProxy:    extNoProxy,
   124  	}
   125  
   126  	cfg := ConfigFile{
   127  		Proxies: map[string]ProxyConfig{
   128  			"default":                       defaultProxyConfig,
   129  			"tcp://example.docker.com:2376": externalProxyConfig,
   130  		},
   131  	}
   132  
   133  	proxyConfig := cfg.ParseProxyConfig("tcp://example.docker.com:2376", nil)
   134  	expected := map[string]*string{
   135  		"HTTP_PROXY":  &extHTTPProxy,
   136  		"http_proxy":  &extHTTPProxy,
   137  		"HTTPS_PROXY": &extHTTPSProxy,
   138  		"https_proxy": &extHTTPSProxy,
   139  		"FTP_PROXY":   &extFTPProxy,
   140  		"ftp_proxy":   &extFTPProxy,
   141  		"NO_PROXY":    &extNoProxy,
   142  		"no_proxy":    &extNoProxy,
   143  	}
   144  	assert.Check(t, is.DeepEqual(expected, proxyConfig))
   145  }
   146  
   147  func TestConfigFile(t *testing.T) {
   148  	configFilename := "configFilename"
   149  	configFile := New(configFilename)
   150  
   151  	assert.Check(t, is.Equal(configFilename, configFile.Filename))
   152  }
   153  
   154  type mockNativeStore struct {
   155  	GetAllCallCount int
   156  	authConfigs     map[string]types.AuthConfig
   157  }
   158  
   159  func (c *mockNativeStore) Erase(registryHostname string) error {
   160  	delete(c.authConfigs, registryHostname)
   161  	return nil
   162  }
   163  
   164  func (c *mockNativeStore) Get(registryHostname string) (types.AuthConfig, error) {
   165  	return c.authConfigs[registryHostname], nil
   166  }
   167  
   168  func (c *mockNativeStore) GetAll() (map[string]types.AuthConfig, error) {
   169  	c.GetAllCallCount = c.GetAllCallCount + 1
   170  	return c.authConfigs, nil
   171  }
   172  
   173  func (c *mockNativeStore) Store(authConfig types.AuthConfig) error {
   174  	return nil
   175  }
   176  
   177  // make sure it satisfies the interface
   178  var _ credentials.Store = (*mockNativeStore)(nil)
   179  
   180  func NewMockNativeStore(authConfigs map[string]types.AuthConfig) credentials.Store {
   181  	return &mockNativeStore{authConfigs: authConfigs}
   182  }
   183  
   184  func TestGetAllCredentialsFileStoreOnly(t *testing.T) {
   185  	configFile := New("filename")
   186  	exampleAuth := types.AuthConfig{
   187  		Username: "user",
   188  		Password: "pass",
   189  	}
   190  	configFile.AuthConfigs["example.com/foo"] = exampleAuth
   191  
   192  	authConfigs, err := configFile.GetAllCredentials()
   193  	assert.NilError(t, err)
   194  
   195  	expected := make(map[string]types.AuthConfig)
   196  	expected["example.com/foo"] = exampleAuth
   197  	assert.Check(t, is.DeepEqual(expected, authConfigs))
   198  }
   199  
   200  func TestGetAllCredentialsCredsStore(t *testing.T) {
   201  	configFile := New("filename")
   202  	configFile.CredentialsStore = "test_creds_store"
   203  	testRegistryHostname := "example.com"
   204  	expectedAuth := types.AuthConfig{
   205  		Username: "user",
   206  		Password: "pass",
   207  	}
   208  
   209  	testCredsStore := NewMockNativeStore(map[string]types.AuthConfig{testRegistryHostname: expectedAuth})
   210  
   211  	tmpNewNativeStore := newNativeStore
   212  	defer func() { newNativeStore = tmpNewNativeStore }()
   213  	newNativeStore = func(configFile *ConfigFile, helperSuffix string) credentials.Store {
   214  		return testCredsStore
   215  	}
   216  
   217  	authConfigs, err := configFile.GetAllCredentials()
   218  	assert.NilError(t, err)
   219  
   220  	expected := make(map[string]types.AuthConfig)
   221  	expected[testRegistryHostname] = expectedAuth
   222  	assert.Check(t, is.DeepEqual(expected, authConfigs))
   223  	assert.Check(t, is.Equal(1, testCredsStore.(*mockNativeStore).GetAllCallCount))
   224  }
   225  
   226  func TestGetAllCredentialsCredHelper(t *testing.T) {
   227  	testCredHelperSuffix := "test_cred_helper"
   228  	testCredHelperRegistryHostname := "credhelper.com"
   229  	testExtraCredHelperRegistryHostname := "somethingweird.com"
   230  
   231  	unexpectedCredHelperAuth := types.AuthConfig{
   232  		Username: "file_store_user",
   233  		Password: "file_store_pass",
   234  	}
   235  	expectedCredHelperAuth := types.AuthConfig{
   236  		Username: "cred_helper_user",
   237  		Password: "cred_helper_pass",
   238  	}
   239  
   240  	configFile := New("filename")
   241  	configFile.CredentialHelpers = map[string]string{testCredHelperRegistryHostname: testCredHelperSuffix}
   242  
   243  	testCredHelper := NewMockNativeStore(map[string]types.AuthConfig{
   244  		testCredHelperRegistryHostname: expectedCredHelperAuth,
   245  		// Add in an extra auth entry which doesn't appear in CredentialHelpers section of the configFile.
   246  		// This verifies that only explicitly configured registries are being requested from the cred helpers.
   247  		testExtraCredHelperRegistryHostname: unexpectedCredHelperAuth,
   248  	})
   249  
   250  	tmpNewNativeStore := newNativeStore
   251  	defer func() { newNativeStore = tmpNewNativeStore }()
   252  	newNativeStore = func(configFile *ConfigFile, helperSuffix string) credentials.Store {
   253  		return testCredHelper
   254  	}
   255  
   256  	authConfigs, err := configFile.GetAllCredentials()
   257  	assert.NilError(t, err)
   258  
   259  	expected := make(map[string]types.AuthConfig)
   260  	expected[testCredHelperRegistryHostname] = expectedCredHelperAuth
   261  	assert.Check(t, is.DeepEqual(expected, authConfigs))
   262  	assert.Check(t, is.Equal(0, testCredHelper.(*mockNativeStore).GetAllCallCount))
   263  }
   264  
   265  func TestGetAllCredentialsFileStoreAndCredHelper(t *testing.T) {
   266  	testFileStoreRegistryHostname := "example.com"
   267  	testCredHelperSuffix := "test_cred_helper"
   268  	testCredHelperRegistryHostname := "credhelper.com"
   269  
   270  	expectedFileStoreAuth := types.AuthConfig{
   271  		Username: "file_store_user",
   272  		Password: "file_store_pass",
   273  	}
   274  	expectedCredHelperAuth := types.AuthConfig{
   275  		Username: "cred_helper_user",
   276  		Password: "cred_helper_pass",
   277  	}
   278  
   279  	configFile := New("filename")
   280  	configFile.CredentialHelpers = map[string]string{testCredHelperRegistryHostname: testCredHelperSuffix}
   281  	configFile.AuthConfigs[testFileStoreRegistryHostname] = expectedFileStoreAuth
   282  
   283  	testCredHelper := NewMockNativeStore(map[string]types.AuthConfig{testCredHelperRegistryHostname: expectedCredHelperAuth})
   284  
   285  	newNativeStore = func(configFile *ConfigFile, helperSuffix string) credentials.Store {
   286  		return testCredHelper
   287  	}
   288  
   289  	tmpNewNativeStore := newNativeStore
   290  	defer func() { newNativeStore = tmpNewNativeStore }()
   291  	authConfigs, err := configFile.GetAllCredentials()
   292  	assert.NilError(t, err)
   293  
   294  	expected := make(map[string]types.AuthConfig)
   295  	expected[testFileStoreRegistryHostname] = expectedFileStoreAuth
   296  	expected[testCredHelperRegistryHostname] = expectedCredHelperAuth
   297  	assert.Check(t, is.DeepEqual(expected, authConfigs))
   298  	assert.Check(t, is.Equal(0, testCredHelper.(*mockNativeStore).GetAllCallCount))
   299  }
   300  
   301  func TestGetAllCredentialsCredStoreAndCredHelper(t *testing.T) {
   302  	testCredStoreSuffix := "test_creds_store"
   303  	testCredStoreRegistryHostname := "credstore.com"
   304  	testCredHelperSuffix := "test_cred_helper"
   305  	testCredHelperRegistryHostname := "credhelper.com"
   306  
   307  	configFile := New("filename")
   308  	configFile.CredentialsStore = testCredStoreSuffix
   309  	configFile.CredentialHelpers = map[string]string{testCredHelperRegistryHostname: testCredHelperSuffix}
   310  
   311  	expectedCredStoreAuth := types.AuthConfig{
   312  		Username: "cred_store_user",
   313  		Password: "cred_store_pass",
   314  	}
   315  	expectedCredHelperAuth := types.AuthConfig{
   316  		Username: "cred_helper_user",
   317  		Password: "cred_helper_pass",
   318  	}
   319  
   320  	testCredHelper := NewMockNativeStore(map[string]types.AuthConfig{testCredHelperRegistryHostname: expectedCredHelperAuth})
   321  	testCredsStore := NewMockNativeStore(map[string]types.AuthConfig{testCredStoreRegistryHostname: expectedCredStoreAuth})
   322  
   323  	tmpNewNativeStore := newNativeStore
   324  	defer func() { newNativeStore = tmpNewNativeStore }()
   325  	newNativeStore = func(configFile *ConfigFile, helperSuffix string) credentials.Store {
   326  		if helperSuffix == testCredHelperSuffix {
   327  			return testCredHelper
   328  		}
   329  		return testCredsStore
   330  	}
   331  
   332  	authConfigs, err := configFile.GetAllCredentials()
   333  	assert.NilError(t, err)
   334  
   335  	expected := make(map[string]types.AuthConfig)
   336  	expected[testCredStoreRegistryHostname] = expectedCredStoreAuth
   337  	expected[testCredHelperRegistryHostname] = expectedCredHelperAuth
   338  	assert.Check(t, is.DeepEqual(expected, authConfigs))
   339  	assert.Check(t, is.Equal(1, testCredsStore.(*mockNativeStore).GetAllCallCount))
   340  	assert.Check(t, is.Equal(0, testCredHelper.(*mockNativeStore).GetAllCallCount))
   341  }
   342  
   343  func TestGetAllCredentialsCredHelperOverridesDefaultStore(t *testing.T) {
   344  	testCredStoreSuffix := "test_creds_store"
   345  	testCredHelperSuffix := "test_cred_helper"
   346  	testRegistryHostname := "example.com"
   347  
   348  	configFile := New("filename")
   349  	configFile.CredentialsStore = testCredStoreSuffix
   350  	configFile.CredentialHelpers = map[string]string{testRegistryHostname: testCredHelperSuffix}
   351  
   352  	unexpectedCredStoreAuth := types.AuthConfig{
   353  		Username: "cred_store_user",
   354  		Password: "cred_store_pass",
   355  	}
   356  	expectedCredHelperAuth := types.AuthConfig{
   357  		Username: "cred_helper_user",
   358  		Password: "cred_helper_pass",
   359  	}
   360  
   361  	testCredHelper := NewMockNativeStore(map[string]types.AuthConfig{testRegistryHostname: expectedCredHelperAuth})
   362  	testCredsStore := NewMockNativeStore(map[string]types.AuthConfig{testRegistryHostname: unexpectedCredStoreAuth})
   363  
   364  	tmpNewNativeStore := newNativeStore
   365  	defer func() { newNativeStore = tmpNewNativeStore }()
   366  	newNativeStore = func(configFile *ConfigFile, helperSuffix string) credentials.Store {
   367  		if helperSuffix == testCredHelperSuffix {
   368  			return testCredHelper
   369  		}
   370  		return testCredsStore
   371  	}
   372  
   373  	authConfigs, err := configFile.GetAllCredentials()
   374  	assert.NilError(t, err)
   375  
   376  	expected := make(map[string]types.AuthConfig)
   377  	expected[testRegistryHostname] = expectedCredHelperAuth
   378  	assert.Check(t, is.DeepEqual(expected, authConfigs))
   379  	assert.Check(t, is.Equal(1, testCredsStore.(*mockNativeStore).GetAllCallCount))
   380  	assert.Check(t, is.Equal(0, testCredHelper.(*mockNativeStore).GetAllCallCount))
   381  }
   382  
   383  func TestCheckKubernetesConfigurationRaiseAnErrorOnInvalidValue(t *testing.T) {
   384  	testCases := []struct {
   385  		name        string
   386  		config      *KubernetesConfig
   387  		expectError bool
   388  	}{
   389  		{
   390  			"no kubernetes config is valid",
   391  			nil,
   392  			false,
   393  		},
   394  		{
   395  			"enabled is valid",
   396  			&KubernetesConfig{AllNamespaces: "enabled"},
   397  			false,
   398  		},
   399  		{
   400  			"disabled is valid",
   401  			&KubernetesConfig{AllNamespaces: "disabled"},
   402  			false,
   403  		},
   404  		{
   405  			"empty string is valid",
   406  			&KubernetesConfig{AllNamespaces: ""},
   407  			false,
   408  		},
   409  		{
   410  			"other value is invalid",
   411  			&KubernetesConfig{AllNamespaces: "unknown"},
   412  			true,
   413  		},
   414  	}
   415  	for _, test := range testCases {
   416  		err := checkKubernetesConfiguration(test.config)
   417  		if test.expectError {
   418  			assert.Assert(t, err != nil, test.name)
   419  		} else {
   420  			assert.NilError(t, err, test.name)
   421  		}
   422  	}
   423  }
   424  
   425  func TestSave(t *testing.T) {
   426  	configFile := New("test-save")
   427  	defer os.Remove("test-save")
   428  	err := configFile.Save()
   429  	assert.NilError(t, err)
   430  	cfg, err := ioutil.ReadFile("test-save")
   431  	assert.NilError(t, err)
   432  	assert.Check(t, is.Equal(string(cfg), "{\n	\"auths\": {}\n}"))
   433  }
   434  
   435  func TestPluginConfig(t *testing.T) {
   436  	configFile := New("test-plugin")
   437  	defer os.Remove("test-plugin")
   438  
   439  	// Populate some initial values
   440  	configFile.SetPluginConfig("plugin1", "data1", "some string")
   441  	configFile.SetPluginConfig("plugin1", "data2", "42")
   442  	configFile.SetPluginConfig("plugin2", "data3", "some other string")
   443  
   444  	// Save a config file with some plugin config
   445  	err := configFile.Save()
   446  	assert.NilError(t, err)
   447  
   448  	// Read it back and check it has the expected content
   449  	cfg, err := ioutil.ReadFile("test-plugin")
   450  	assert.NilError(t, err)
   451  	golden.Assert(t, string(cfg), "plugin-config.golden")
   452  
   453  	// Load it, resave and check again that the content is
   454  	// preserved through a load/save cycle.
   455  	configFile = New("test-plugin2")
   456  	defer os.Remove("test-plugin2")
   457  	assert.NilError(t, configFile.LoadFromReader(bytes.NewReader(cfg)))
   458  	err = configFile.Save()
   459  	assert.NilError(t, err)
   460  	cfg, err = ioutil.ReadFile("test-plugin2")
   461  	assert.NilError(t, err)
   462  	golden.Assert(t, string(cfg), "plugin-config.golden")
   463  
   464  	// Check that the contents was reloaded properly
   465  	v, ok := configFile.PluginConfig("plugin1", "data1")
   466  	assert.Assert(t, ok)
   467  	assert.Equal(t, v, "some string")
   468  	v, ok = configFile.PluginConfig("plugin1", "data2")
   469  	assert.Assert(t, ok)
   470  	assert.Equal(t, v, "42")
   471  	v, ok = configFile.PluginConfig("plugin1", "data3")
   472  	assert.Assert(t, !ok)
   473  	assert.Equal(t, v, "")
   474  	v, ok = configFile.PluginConfig("plugin2", "data3")
   475  	assert.Assert(t, ok)
   476  	assert.Equal(t, v, "some other string")
   477  	v, ok = configFile.PluginConfig("plugin2", "data4")
   478  	assert.Assert(t, !ok)
   479  	assert.Equal(t, v, "")
   480  	v, ok = configFile.PluginConfig("plugin3", "data5")
   481  	assert.Assert(t, !ok)
   482  	assert.Equal(t, v, "")
   483  
   484  	// Add, remove and modify
   485  	configFile.SetPluginConfig("plugin1", "data1", "some replacement string") // replacing a key
   486  	configFile.SetPluginConfig("plugin1", "data2", "")                        // deleting a key
   487  	configFile.SetPluginConfig("plugin1", "data3", "some additional string")  // new key
   488  	configFile.SetPluginConfig("plugin2", "data3", "")                        // delete the whole plugin, since this was the only data
   489  	configFile.SetPluginConfig("plugin3", "data5", "a new plugin")            // add a new plugin
   490  
   491  	err = configFile.Save()
   492  	assert.NilError(t, err)
   493  
   494  	// Read it back and check it has the expected content again
   495  	cfg, err = ioutil.ReadFile("test-plugin2")
   496  	assert.NilError(t, err)
   497  	golden.Assert(t, string(cfg), "plugin-config-2.golden")
   498  }