github.com/panekj/cli@v0.0.0-20230304125325-467dd2f3797e/cli/config/config_test.go (about) 1 package config 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "path/filepath" 8 "runtime" 9 "strings" 10 "testing" 11 12 "github.com/docker/cli/cli/config/configfile" 13 "github.com/docker/cli/cli/config/credentials" 14 "github.com/docker/cli/cli/config/types" 15 "gotest.tools/v3/assert" 16 is "gotest.tools/v3/assert/cmp" 17 "gotest.tools/v3/env" 18 "gotest.tools/v3/fs" 19 ) 20 21 var homeKey = "HOME" 22 23 func init() { 24 if runtime.GOOS == "windows" { 25 homeKey = "USERPROFILE" 26 } 27 } 28 29 func setupConfigDir(t *testing.T) string { 30 tmpdir := t.TempDir() 31 oldDir := Dir() 32 SetDir(tmpdir) 33 t.Cleanup(func() { 34 SetDir(oldDir) 35 }) 36 return tmpdir 37 } 38 39 func TestEmptyConfigDir(t *testing.T) { 40 tmpHome := setupConfigDir(t) 41 42 config, err := Load("") 43 assert.NilError(t, err) 44 45 expectedConfigFilename := filepath.Join(tmpHome, ConfigFileName) 46 assert.Check(t, is.Equal(expectedConfigFilename, config.Filename)) 47 48 // Now save it and make sure it shows up in new form 49 saveConfigAndValidateNewFormat(t, config, tmpHome) 50 } 51 52 func TestMissingFile(t *testing.T) { 53 tmpHome := t.TempDir() 54 55 config, err := Load(tmpHome) 56 assert.NilError(t, err) 57 58 // Now save it and make sure it shows up in new form 59 saveConfigAndValidateNewFormat(t, config, tmpHome) 60 } 61 62 func TestSaveFileToDirs(t *testing.T) { 63 tmpHome := filepath.Join(t.TempDir(), ".docker") 64 config, err := Load(tmpHome) 65 assert.NilError(t, err) 66 67 // Now save it and make sure it shows up in new form 68 saveConfigAndValidateNewFormat(t, config, tmpHome) 69 } 70 71 func TestEmptyFile(t *testing.T) { 72 tmpHome := t.TempDir() 73 74 fn := filepath.Join(tmpHome, ConfigFileName) 75 err := os.WriteFile(fn, []byte(""), 0o600) 76 assert.NilError(t, err) 77 78 _, err = Load(tmpHome) 79 assert.NilError(t, err) 80 } 81 82 func TestEmptyJSON(t *testing.T) { 83 tmpHome := t.TempDir() 84 85 fn := filepath.Join(tmpHome, ConfigFileName) 86 err := os.WriteFile(fn, []byte("{}"), 0o600) 87 assert.NilError(t, err) 88 89 config, err := Load(tmpHome) 90 assert.NilError(t, err) 91 92 // Now save it and make sure it shows up in new form 93 saveConfigAndValidateNewFormat(t, config, tmpHome) 94 } 95 96 func TestOldJSONFallbackDeprecationWarning(t *testing.T) { 97 js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}` 98 tmpHome := fs.NewDir(t, t.Name(), fs.WithFile(oldConfigfile, js)) 99 defer tmpHome.Remove() 100 env.PatchAll(t, map[string]string{homeKey: tmpHome.Path(), "DOCKER_CONFIG": ""}) 101 102 // reset the homeDir, configDir, and its sync.Once, to force them being resolved again 103 resetHomeDir() 104 resetConfigDir() 105 106 buffer := new(bytes.Buffer) 107 configFile := LoadDefaultConfigFile(buffer) 108 expected := configfile.New(tmpHome.Join(configFileDir, ConfigFileName)) 109 expected.AuthConfigs = map[string]types.AuthConfig{} 110 assert.Assert(t, strings.Contains(buffer.String(), "WARNING: Support for the legacy ~/.dockercfg configuration file and file-format has been removed and the configuration file will be ignored")) 111 assert.Check(t, is.DeepEqual(expected, configFile)) 112 } 113 114 func TestNewJSON(t *testing.T) { 115 tmpHome := t.TempDir() 116 117 fn := filepath.Join(tmpHome, ConfigFileName) 118 js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } } }` 119 if err := os.WriteFile(fn, []byte(js), 0o600); err != nil { 120 t.Fatal(err) 121 } 122 123 config, err := Load(tmpHome) 124 assert.NilError(t, err) 125 126 ac := config.AuthConfigs["https://index.docker.io/v1/"] 127 assert.Equal(t, ac.Username, "joejoe") 128 assert.Equal(t, ac.Password, "hello") 129 130 // Now save it and make sure it shows up in new form 131 configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) 132 133 expConfStr := `{ 134 "auths": { 135 "https://index.docker.io/v1/": { 136 "auth": "am9lam9lOmhlbGxv" 137 } 138 } 139 }` 140 141 if configStr != expConfStr { 142 t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr) 143 } 144 } 145 146 func TestNewJSONNoEmail(t *testing.T) { 147 tmpHome := t.TempDir() 148 149 fn := filepath.Join(tmpHome, ConfigFileName) 150 js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } } }` 151 if err := os.WriteFile(fn, []byte(js), 0o600); err != nil { 152 t.Fatal(err) 153 } 154 155 config, err := Load(tmpHome) 156 assert.NilError(t, err) 157 158 ac := config.AuthConfigs["https://index.docker.io/v1/"] 159 assert.Equal(t, ac.Username, "joejoe") 160 assert.Equal(t, ac.Password, "hello") 161 162 // Now save it and make sure it shows up in new form 163 configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) 164 165 expConfStr := `{ 166 "auths": { 167 "https://index.docker.io/v1/": { 168 "auth": "am9lam9lOmhlbGxv" 169 } 170 } 171 }` 172 173 if configStr != expConfStr { 174 t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr) 175 } 176 } 177 178 func TestJSONWithPsFormat(t *testing.T) { 179 tmpHome := t.TempDir() 180 181 fn := filepath.Join(tmpHome, ConfigFileName) 182 js := `{ 183 "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } }, 184 "psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}" 185 }` 186 if err := os.WriteFile(fn, []byte(js), 0o600); err != nil { 187 t.Fatal(err) 188 } 189 190 config, err := Load(tmpHome) 191 assert.NilError(t, err) 192 193 if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` { 194 t.Fatalf("Unknown ps format: %s\n", config.PsFormat) 195 } 196 197 // Now save it and make sure it shows up in new form 198 configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) 199 if !strings.Contains(configStr, `"psFormat":`) || 200 !strings.Contains(configStr, "{{.ID}}") { 201 t.Fatalf("Should have save in new form: %s", configStr) 202 } 203 } 204 205 func TestJSONWithCredentialStore(t *testing.T) { 206 tmpHome := t.TempDir() 207 208 fn := filepath.Join(tmpHome, ConfigFileName) 209 js := `{ 210 "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } }, 211 "credsStore": "crazy-secure-storage" 212 }` 213 if err := os.WriteFile(fn, []byte(js), 0o600); err != nil { 214 t.Fatal(err) 215 } 216 217 config, err := Load(tmpHome) 218 assert.NilError(t, err) 219 220 if config.CredentialsStore != "crazy-secure-storage" { 221 t.Fatalf("Unknown credential store: %s\n", config.CredentialsStore) 222 } 223 224 // Now save it and make sure it shows up in new form 225 configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) 226 if !strings.Contains(configStr, `"credsStore":`) || 227 !strings.Contains(configStr, "crazy-secure-storage") { 228 t.Fatalf("Should have save in new form: %s", configStr) 229 } 230 } 231 232 func TestJSONWithCredentialHelpers(t *testing.T) { 233 tmpHome := t.TempDir() 234 235 fn := filepath.Join(tmpHome, ConfigFileName) 236 js := `{ 237 "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } }, 238 "credHelpers": { "images.io": "images-io", "containers.com": "crazy-secure-storage" } 239 }` 240 if err := os.WriteFile(fn, []byte(js), 0o600); err != nil { 241 t.Fatal(err) 242 } 243 244 config, err := Load(tmpHome) 245 assert.NilError(t, err) 246 247 if config.CredentialHelpers == nil { 248 t.Fatal("config.CredentialHelpers was nil") 249 } else if config.CredentialHelpers["images.io"] != "images-io" || 250 config.CredentialHelpers["containers.com"] != "crazy-secure-storage" { 251 t.Fatalf("Credential helpers not deserialized properly: %v\n", config.CredentialHelpers) 252 } 253 254 // Now save it and make sure it shows up in new form 255 configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) 256 if !strings.Contains(configStr, `"credHelpers":`) || 257 !strings.Contains(configStr, "images.io") || 258 !strings.Contains(configStr, "images-io") || 259 !strings.Contains(configStr, "containers.com") || 260 !strings.Contains(configStr, "crazy-secure-storage") { 261 t.Fatalf("Should have save in new form: %s", configStr) 262 } 263 } 264 265 // Save it and make sure it shows up in new form 266 func saveConfigAndValidateNewFormat(t *testing.T, config *configfile.ConfigFile, configDir string) string { 267 t.Helper() 268 assert.NilError(t, config.Save()) 269 270 buf, err := os.ReadFile(filepath.Join(configDir, ConfigFileName)) 271 assert.NilError(t, err) 272 assert.Check(t, is.Contains(string(buf), `"auths":`)) 273 return string(buf) 274 } 275 276 func TestConfigDir(t *testing.T) { 277 tmpHome := t.TempDir() 278 279 if Dir() == tmpHome { 280 t.Fatalf("Expected ConfigDir to be different than %s by default, but was the same", tmpHome) 281 } 282 283 // Update configDir 284 SetDir(tmpHome) 285 286 if Dir() != tmpHome { 287 t.Fatalf("Expected ConfigDir to %s, but was %s", tmpHome, Dir()) 288 } 289 } 290 291 func TestJSONReaderNoFile(t *testing.T) { 292 js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }` 293 294 config, err := LoadFromReader(strings.NewReader(js)) 295 assert.NilError(t, err) 296 297 ac := config.AuthConfigs["https://index.docker.io/v1/"] 298 assert.Equal(t, ac.Username, "joejoe") 299 assert.Equal(t, ac.Password, "hello") 300 } 301 302 func TestJSONWithPsFormatNoFile(t *testing.T) { 303 js := `{ 304 "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } }, 305 "psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}" 306 }` 307 config, err := LoadFromReader(strings.NewReader(js)) 308 assert.NilError(t, err) 309 310 if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` { 311 t.Fatalf("Unknown ps format: %s\n", config.PsFormat) 312 } 313 } 314 315 func TestJSONSaveWithNoFile(t *testing.T) { 316 js := `{ 317 "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } }, 318 "psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}" 319 }` 320 config, err := LoadFromReader(strings.NewReader(js)) 321 assert.NilError(t, err) 322 err = config.Save() 323 assert.ErrorContains(t, err, "with empty filename") 324 325 tmpHome := t.TempDir() 326 327 fn := filepath.Join(tmpHome, ConfigFileName) 328 f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) 329 defer f.Close() 330 331 assert.NilError(t, config.SaveToWriter(f)) 332 buf, err := os.ReadFile(filepath.Join(tmpHome, ConfigFileName)) 333 assert.NilError(t, err) 334 expConfStr := `{ 335 "auths": { 336 "https://index.docker.io/v1/": { 337 "auth": "am9lam9lOmhlbGxv" 338 } 339 }, 340 "psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}" 341 }` 342 if string(buf) != expConfStr { 343 t.Fatalf("Should have save in new form: \n%s\nnot \n%s", string(buf), expConfStr) 344 } 345 } 346 347 func TestLoadDefaultConfigFile(t *testing.T) { 348 dir := setupConfigDir(t) 349 buffer := new(bytes.Buffer) 350 351 filename := filepath.Join(dir, ConfigFileName) 352 content := []byte(`{"PsFormat": "format"}`) 353 err := os.WriteFile(filename, content, 0o644) 354 assert.NilError(t, err) 355 356 configFile := LoadDefaultConfigFile(buffer) 357 credStore := credentials.DetectDefaultStore("") 358 expected := configfile.New(filename) 359 expected.CredentialsStore = credStore 360 expected.PsFormat = "format" 361 362 assert.Check(t, is.DeepEqual(expected, configFile)) 363 } 364 365 func TestConfigPath(t *testing.T) { 366 oldDir := Dir() 367 368 for _, tc := range []struct { 369 name string 370 dir string 371 path []string 372 expected string 373 expectedErr string 374 }{ 375 { 376 name: "valid_path", 377 dir: "dummy", 378 path: []string{"a", "b"}, 379 expected: filepath.Join("dummy", "a", "b"), 380 }, 381 { 382 name: "valid_path_absolute_dir", 383 dir: "/dummy", 384 path: []string{"a", "b"}, 385 expected: filepath.Join("/dummy", "a", "b"), 386 }, 387 { 388 name: "invalid_relative_path", 389 dir: "dummy", 390 path: []string{"e", "..", "..", "f"}, 391 expectedErr: fmt.Sprintf("is outside of root config directory %q", "dummy"), 392 }, 393 { 394 name: "invalid_absolute_path", 395 dir: "dummy", 396 path: []string{"/a", "..", ".."}, 397 expectedErr: fmt.Sprintf("is outside of root config directory %q", "dummy"), 398 }, 399 } { 400 tc := tc 401 t.Run(tc.name, func(t *testing.T) { 402 SetDir(tc.dir) 403 f, err := Path(tc.path...) 404 assert.Equal(t, f, tc.expected) 405 if tc.expectedErr == "" { 406 assert.NilError(t, err) 407 } else { 408 assert.ErrorContains(t, err, tc.expectedErr) 409 } 410 }) 411 } 412 413 SetDir(oldDir) 414 }