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 }