github.com/xeptore/docker-cli@v20.10.14+incompatible/cli/config/config_test.go (about) 1 package config 2 3 import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "runtime" 10 "strings" 11 "testing" 12 13 "github.com/docker/cli/cli/config/configfile" 14 "github.com/docker/cli/cli/config/credentials" 15 "github.com/docker/cli/cli/config/types" 16 "gotest.tools/v3/assert" 17 is "gotest.tools/v3/assert/cmp" 18 "gotest.tools/v3/env" 19 "gotest.tools/v3/fs" 20 ) 21 22 var homeKey = "HOME" 23 24 func init() { 25 if runtime.GOOS == "windows" { 26 homeKey = "USERPROFILE" 27 } 28 } 29 30 func setupConfigDir(t *testing.T) (string, func()) { 31 tmpdir, err := ioutil.TempDir("", "config-test") 32 assert.NilError(t, err) 33 oldDir := Dir() 34 SetDir(tmpdir) 35 36 return tmpdir, func() { 37 SetDir(oldDir) 38 os.RemoveAll(tmpdir) 39 } 40 } 41 42 func TestEmptyConfigDir(t *testing.T) { 43 tmpHome, cleanup := setupConfigDir(t) 44 defer cleanup() 45 46 config, err := Load("") 47 assert.NilError(t, err) 48 49 expectedConfigFilename := filepath.Join(tmpHome, ConfigFileName) 50 assert.Check(t, is.Equal(expectedConfigFilename, config.Filename)) 51 52 // Now save it and make sure it shows up in new form 53 saveConfigAndValidateNewFormat(t, config, tmpHome) 54 } 55 56 func TestMissingFile(t *testing.T) { 57 tmpHome, err := ioutil.TempDir("", "config-test") 58 assert.NilError(t, err) 59 defer os.RemoveAll(tmpHome) 60 61 config, err := Load(tmpHome) 62 assert.NilError(t, err) 63 64 // Now save it and make sure it shows up in new form 65 saveConfigAndValidateNewFormat(t, config, tmpHome) 66 } 67 68 func TestSaveFileToDirs(t *testing.T) { 69 tmpHome, err := ioutil.TempDir("", "config-test") 70 assert.NilError(t, err) 71 defer os.RemoveAll(tmpHome) 72 73 tmpHome += "/.docker" 74 75 config, err := Load(tmpHome) 76 assert.NilError(t, err) 77 78 // Now save it and make sure it shows up in new form 79 saveConfigAndValidateNewFormat(t, config, tmpHome) 80 } 81 82 func TestEmptyFile(t *testing.T) { 83 tmpHome, err := ioutil.TempDir("", "config-test") 84 assert.NilError(t, err) 85 defer os.RemoveAll(tmpHome) 86 87 fn := filepath.Join(tmpHome, ConfigFileName) 88 err = ioutil.WriteFile(fn, []byte(""), 0600) 89 assert.NilError(t, err) 90 91 _, err = Load(tmpHome) 92 assert.NilError(t, err) 93 } 94 95 func TestEmptyJSON(t *testing.T) { 96 tmpHome, err := ioutil.TempDir("", "config-test") 97 assert.NilError(t, err) 98 defer os.RemoveAll(tmpHome) 99 100 fn := filepath.Join(tmpHome, ConfigFileName) 101 err = ioutil.WriteFile(fn, []byte("{}"), 0600) 102 assert.NilError(t, err) 103 104 config, err := Load(tmpHome) 105 assert.NilError(t, err) 106 107 // Now save it and make sure it shows up in new form 108 saveConfigAndValidateNewFormat(t, config, tmpHome) 109 } 110 111 func TestOldInvalidsAuth(t *testing.T) { 112 invalids := map[string]string{ 113 `username = test`: "The Auth config file is empty", 114 `username 115 password`: "Invalid Auth config file", 116 `username = test 117 email`: "Invalid auth configuration file", 118 } 119 120 resetHomeDir() 121 tmpHome, err := ioutil.TempDir("", "config-test") 122 assert.NilError(t, err) 123 defer os.RemoveAll(tmpHome) 124 defer env.Patch(t, homeKey, tmpHome)() 125 126 for content, expectedError := range invalids { 127 fn := filepath.Join(tmpHome, oldConfigfile) 128 err := ioutil.WriteFile(fn, []byte(content), 0600) 129 assert.NilError(t, err) 130 131 _, err = Load(tmpHome) 132 assert.ErrorContains(t, err, expectedError) 133 } 134 } 135 136 func TestOldValidAuth(t *testing.T) { 137 resetHomeDir() 138 tmpHome, err := ioutil.TempDir("", "config-test") 139 assert.NilError(t, err) 140 defer os.RemoveAll(tmpHome) 141 defer env.Patch(t, homeKey, tmpHome)() 142 143 fn := filepath.Join(tmpHome, oldConfigfile) 144 js := `username = am9lam9lOmhlbGxv 145 email = user@example.com` 146 err = ioutil.WriteFile(fn, []byte(js), 0600) 147 assert.NilError(t, err) 148 149 config, err := Load(tmpHome) 150 assert.NilError(t, err) 151 152 // defaultIndexserver is https://index.docker.io/v1/ 153 ac := config.AuthConfigs["https://index.docker.io/v1/"] 154 assert.Equal(t, ac.Username, "joejoe") 155 assert.Equal(t, ac.Password, "hello") 156 157 // Now save it and make sure it shows up in new form 158 configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) 159 160 expConfStr := `{ 161 "auths": { 162 "https://index.docker.io/v1/": { 163 "auth": "am9lam9lOmhlbGxv" 164 } 165 } 166 }` 167 168 assert.Check(t, is.Equal(expConfStr, configStr)) 169 } 170 171 func TestOldJSONInvalid(t *testing.T) { 172 resetHomeDir() 173 tmpHome, err := ioutil.TempDir("", "config-test") 174 assert.NilError(t, err) 175 defer os.RemoveAll(tmpHome) 176 defer env.Patch(t, homeKey, tmpHome)() 177 178 fn := filepath.Join(tmpHome, oldConfigfile) 179 js := `{"https://index.docker.io/v1/":{"auth":"test","email":"user@example.com"}}` 180 if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil { 181 t.Fatal(err) 182 } 183 184 config, err := Load(tmpHome) 185 // Use Contains instead of == since the file name will change each time 186 if err == nil || !strings.Contains(err.Error(), "Invalid auth configuration file") { 187 t.Fatalf("Expected an error got : %v, %v", config, err) 188 } 189 } 190 191 func TestOldJSON(t *testing.T) { 192 resetHomeDir() 193 tmpHome, err := ioutil.TempDir("", "config-test") 194 assert.NilError(t, err) 195 defer os.RemoveAll(tmpHome) 196 defer env.Patch(t, homeKey, tmpHome)() 197 198 fn := filepath.Join(tmpHome, oldConfigfile) 199 js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}` 200 if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil { 201 t.Fatal(err) 202 } 203 204 config, err := Load(tmpHome) 205 assert.NilError(t, err) 206 207 ac := config.AuthConfigs["https://index.docker.io/v1/"] 208 assert.Equal(t, ac.Username, "joejoe") 209 assert.Equal(t, ac.Password, "hello") 210 211 // Now save it and make sure it shows up in new form 212 configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) 213 214 expConfStr := `{ 215 "auths": { 216 "https://index.docker.io/v1/": { 217 "auth": "am9lam9lOmhlbGxv", 218 "email": "user@example.com" 219 } 220 } 221 }` 222 223 if configStr != expConfStr { 224 t.Fatalf("Should have save in new form: \n'%s'\n not \n'%s'\n", configStr, expConfStr) 225 } 226 } 227 228 func TestOldJSONFallbackDeprecationWarning(t *testing.T) { 229 js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}` 230 tmpHome := fs.NewDir(t, t.Name(), fs.WithFile(oldConfigfile, js)) 231 defer tmpHome.Remove() 232 defer env.PatchAll(t, map[string]string{homeKey: tmpHome.Path(), "DOCKER_CONFIG": ""})() 233 234 // reset the homeDir, configDir, and its sync.Once, to force them being resolved again 235 resetHomeDir() 236 resetConfigDir() 237 238 buffer := new(bytes.Buffer) 239 configFile := LoadDefaultConfigFile(buffer) 240 expected := configfile.New(tmpHome.Join(configFileDir, ConfigFileName)) 241 expected.AuthConfigs = map[string]types.AuthConfig{ 242 "https://index.docker.io/v1/": { 243 Username: "joejoe", 244 Password: "hello", 245 Email: "user@example.com", 246 ServerAddress: "https://index.docker.io/v1/", 247 }, 248 } 249 assert.Assert(t, strings.Contains(buffer.String(), "WARNING: Support for the legacy ~/.dockercfg configuration file and file-format is deprecated and will be removed in an upcoming release")) 250 assert.Check(t, is.DeepEqual(expected, configFile)) 251 } 252 253 func TestNewJSON(t *testing.T) { 254 tmpHome, err := ioutil.TempDir("", "config-test") 255 assert.NilError(t, err) 256 defer os.RemoveAll(tmpHome) 257 258 fn := filepath.Join(tmpHome, ConfigFileName) 259 js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } } }` 260 if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil { 261 t.Fatal(err) 262 } 263 264 config, err := Load(tmpHome) 265 assert.NilError(t, err) 266 267 ac := config.AuthConfigs["https://index.docker.io/v1/"] 268 assert.Equal(t, ac.Username, "joejoe") 269 assert.Equal(t, ac.Password, "hello") 270 271 // Now save it and make sure it shows up in new form 272 configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) 273 274 expConfStr := `{ 275 "auths": { 276 "https://index.docker.io/v1/": { 277 "auth": "am9lam9lOmhlbGxv" 278 } 279 } 280 }` 281 282 if configStr != expConfStr { 283 t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr) 284 } 285 } 286 287 func TestNewJSONNoEmail(t *testing.T) { 288 tmpHome, err := ioutil.TempDir("", "config-test") 289 assert.NilError(t, err) 290 defer os.RemoveAll(tmpHome) 291 292 fn := filepath.Join(tmpHome, ConfigFileName) 293 js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } } }` 294 if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil { 295 t.Fatal(err) 296 } 297 298 config, err := Load(tmpHome) 299 assert.NilError(t, err) 300 301 ac := config.AuthConfigs["https://index.docker.io/v1/"] 302 assert.Equal(t, ac.Username, "joejoe") 303 assert.Equal(t, ac.Password, "hello") 304 305 // Now save it and make sure it shows up in new form 306 configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) 307 308 expConfStr := `{ 309 "auths": { 310 "https://index.docker.io/v1/": { 311 "auth": "am9lam9lOmhlbGxv" 312 } 313 } 314 }` 315 316 if configStr != expConfStr { 317 t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr) 318 } 319 } 320 321 func TestJSONWithPsFormat(t *testing.T) { 322 tmpHome, err := ioutil.TempDir("", "config-test") 323 assert.NilError(t, err) 324 defer os.RemoveAll(tmpHome) 325 326 fn := filepath.Join(tmpHome, ConfigFileName) 327 js := `{ 328 "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } }, 329 "psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}" 330 }` 331 if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil { 332 t.Fatal(err) 333 } 334 335 config, err := Load(tmpHome) 336 assert.NilError(t, err) 337 338 if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` { 339 t.Fatalf("Unknown ps format: %s\n", config.PsFormat) 340 } 341 342 // Now save it and make sure it shows up in new form 343 configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) 344 if !strings.Contains(configStr, `"psFormat":`) || 345 !strings.Contains(configStr, "{{.ID}}") { 346 t.Fatalf("Should have save in new form: %s", configStr) 347 } 348 } 349 350 func TestJSONWithCredentialStore(t *testing.T) { 351 tmpHome, err := ioutil.TempDir("", "config-test") 352 assert.NilError(t, err) 353 defer os.RemoveAll(tmpHome) 354 355 fn := filepath.Join(tmpHome, ConfigFileName) 356 js := `{ 357 "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } }, 358 "credsStore": "crazy-secure-storage" 359 }` 360 if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil { 361 t.Fatal(err) 362 } 363 364 config, err := Load(tmpHome) 365 assert.NilError(t, err) 366 367 if config.CredentialsStore != "crazy-secure-storage" { 368 t.Fatalf("Unknown credential store: %s\n", config.CredentialsStore) 369 } 370 371 // Now save it and make sure it shows up in new form 372 configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) 373 if !strings.Contains(configStr, `"credsStore":`) || 374 !strings.Contains(configStr, "crazy-secure-storage") { 375 t.Fatalf("Should have save in new form: %s", configStr) 376 } 377 } 378 379 func TestJSONWithCredentialHelpers(t *testing.T) { 380 tmpHome, err := ioutil.TempDir("", "config-test") 381 assert.NilError(t, err) 382 defer os.RemoveAll(tmpHome) 383 384 fn := filepath.Join(tmpHome, ConfigFileName) 385 js := `{ 386 "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } }, 387 "credHelpers": { "images.io": "images-io", "containers.com": "crazy-secure-storage" } 388 }` 389 if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil { 390 t.Fatal(err) 391 } 392 393 config, err := Load(tmpHome) 394 assert.NilError(t, err) 395 396 if config.CredentialHelpers == nil { 397 t.Fatal("config.CredentialHelpers was nil") 398 } else if config.CredentialHelpers["images.io"] != "images-io" || 399 config.CredentialHelpers["containers.com"] != "crazy-secure-storage" { 400 t.Fatalf("Credential helpers not deserialized properly: %v\n", config.CredentialHelpers) 401 } 402 403 // Now save it and make sure it shows up in new form 404 configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) 405 if !strings.Contains(configStr, `"credHelpers":`) || 406 !strings.Contains(configStr, "images.io") || 407 !strings.Contains(configStr, "images-io") || 408 !strings.Contains(configStr, "containers.com") || 409 !strings.Contains(configStr, "crazy-secure-storage") { 410 t.Fatalf("Should have save in new form: %s", configStr) 411 } 412 } 413 414 // Save it and make sure it shows up in new form 415 func saveConfigAndValidateNewFormat(t *testing.T, config *configfile.ConfigFile, configDir string) string { 416 t.Helper() 417 assert.NilError(t, config.Save()) 418 419 buf, err := ioutil.ReadFile(filepath.Join(configDir, ConfigFileName)) 420 assert.NilError(t, err) 421 assert.Check(t, is.Contains(string(buf), `"auths":`)) 422 return string(buf) 423 } 424 425 func TestConfigDir(t *testing.T) { 426 tmpHome, err := ioutil.TempDir("", "config-test") 427 assert.NilError(t, err) 428 defer os.RemoveAll(tmpHome) 429 430 if Dir() == tmpHome { 431 t.Fatalf("Expected ConfigDir to be different than %s by default, but was the same", tmpHome) 432 } 433 434 // Update configDir 435 SetDir(tmpHome) 436 437 if Dir() != tmpHome { 438 t.Fatalf("Expected ConfigDir to %s, but was %s", tmpHome, Dir()) 439 } 440 } 441 442 func TestJSONReaderNoFile(t *testing.T) { 443 js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }` 444 445 config, err := LoadFromReader(strings.NewReader(js)) 446 assert.NilError(t, err) 447 448 ac := config.AuthConfigs["https://index.docker.io/v1/"] 449 assert.Equal(t, ac.Username, "joejoe") 450 assert.Equal(t, ac.Password, "hello") 451 } 452 453 func TestOldJSONReaderNoFile(t *testing.T) { 454 js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}` 455 456 config, err := LegacyLoadFromReader(strings.NewReader(js)) 457 assert.NilError(t, err) 458 459 ac := config.AuthConfigs["https://index.docker.io/v1/"] 460 assert.Equal(t, ac.Username, "joejoe") 461 assert.Equal(t, ac.Password, "hello") 462 } 463 464 func TestJSONWithPsFormatNoFile(t *testing.T) { 465 js := `{ 466 "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } }, 467 "psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}" 468 }` 469 config, err := LoadFromReader(strings.NewReader(js)) 470 assert.NilError(t, err) 471 472 if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` { 473 t.Fatalf("Unknown ps format: %s\n", config.PsFormat) 474 } 475 } 476 477 func TestJSONSaveWithNoFile(t *testing.T) { 478 js := `{ 479 "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } }, 480 "psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}" 481 }` 482 config, err := LoadFromReader(strings.NewReader(js)) 483 assert.NilError(t, err) 484 err = config.Save() 485 assert.ErrorContains(t, err, "with empty filename") 486 487 tmpHome, err := ioutil.TempDir("", "config-test") 488 assert.NilError(t, err) 489 defer os.RemoveAll(tmpHome) 490 491 fn := filepath.Join(tmpHome, ConfigFileName) 492 f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) 493 defer f.Close() 494 495 assert.NilError(t, config.SaveToWriter(f)) 496 buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName)) 497 assert.NilError(t, err) 498 expConfStr := `{ 499 "auths": { 500 "https://index.docker.io/v1/": { 501 "auth": "am9lam9lOmhlbGxv" 502 } 503 }, 504 "psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}" 505 }` 506 if string(buf) != expConfStr { 507 t.Fatalf("Should have save in new form: \n%s\nnot \n%s", string(buf), expConfStr) 508 } 509 } 510 511 func TestLegacyJSONSaveWithNoFile(t *testing.T) { 512 js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}` 513 config, err := LegacyLoadFromReader(strings.NewReader(js)) 514 assert.NilError(t, err) 515 err = config.Save() 516 assert.ErrorContains(t, err, "with empty filename") 517 518 tmpHome, err := ioutil.TempDir("", "config-test") 519 assert.NilError(t, err) 520 defer os.RemoveAll(tmpHome) 521 522 fn := filepath.Join(tmpHome, ConfigFileName) 523 f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) 524 defer f.Close() 525 526 assert.NilError(t, config.SaveToWriter(f)) 527 buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName)) 528 assert.NilError(t, err) 529 530 expConfStr := `{ 531 "auths": { 532 "https://index.docker.io/v1/": { 533 "auth": "am9lam9lOmhlbGxv", 534 "email": "user@example.com" 535 } 536 } 537 }` 538 539 if string(buf) != expConfStr { 540 t.Fatalf("Should have save in new form: \n%s\n not \n%s", string(buf), expConfStr) 541 } 542 } 543 544 func TestLoadDefaultConfigFile(t *testing.T) { 545 dir, cleanup := setupConfigDir(t) 546 defer cleanup() 547 buffer := new(bytes.Buffer) 548 549 filename := filepath.Join(dir, ConfigFileName) 550 content := []byte(`{"PsFormat": "format"}`) 551 err := ioutil.WriteFile(filename, content, 0644) 552 assert.NilError(t, err) 553 554 configFile := LoadDefaultConfigFile(buffer) 555 credStore := credentials.DetectDefaultStore("") 556 expected := configfile.New(filename) 557 expected.CredentialsStore = credStore 558 expected.PsFormat = "format" 559 560 assert.Check(t, is.DeepEqual(expected, configFile)) 561 } 562 563 func TestConfigPath(t *testing.T) { 564 oldDir := Dir() 565 566 for _, tc := range []struct { 567 name string 568 dir string 569 path []string 570 expected string 571 expectedErr string 572 }{ 573 { 574 name: "valid_path", 575 dir: "dummy", 576 path: []string{"a", "b"}, 577 expected: filepath.Join("dummy", "a", "b"), 578 }, 579 { 580 name: "valid_path_absolute_dir", 581 dir: "/dummy", 582 path: []string{"a", "b"}, 583 expected: filepath.Join("/dummy", "a", "b"), 584 }, 585 { 586 name: "invalid_relative_path", 587 dir: "dummy", 588 path: []string{"e", "..", "..", "f"}, 589 expectedErr: fmt.Sprintf("is outside of root config directory %q", "dummy"), 590 }, 591 { 592 name: "invalid_absolute_path", 593 dir: "dummy", 594 path: []string{"/a", "..", ".."}, 595 expectedErr: fmt.Sprintf("is outside of root config directory %q", "dummy"), 596 }, 597 } { 598 tc := tc 599 t.Run(tc.name, func(t *testing.T) { 600 SetDir(tc.dir) 601 f, err := Path(tc.path...) 602 assert.Equal(t, f, tc.expected) 603 if tc.expectedErr == "" { 604 assert.NilError(t, err) 605 } else { 606 assert.ErrorContains(t, err, tc.expectedErr) 607 } 608 }) 609 } 610 611 SetDir(oldDir) 612 }