github.com/xeptore/docker-cli@v20.10.14+incompatible/cli/config/configfile/file_test.go (about)

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