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