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