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