github.com/moby/docker@v26.1.3+incompatible/daemon/config/config_test.go (about) 1 package config // import "github.com/docker/docker/daemon/config" 2 3 import ( 4 "encoding/json" 5 "os" 6 "path/filepath" 7 "reflect" 8 "strings" 9 "testing" 10 11 "dario.cat/mergo" 12 "github.com/docker/docker/api" 13 "github.com/docker/docker/libnetwork/ipamutils" 14 "github.com/docker/docker/opts" 15 "github.com/google/go-cmp/cmp" 16 "github.com/google/go-cmp/cmp/cmpopts" 17 "github.com/spf13/pflag" 18 "golang.org/x/text/encoding" 19 "golang.org/x/text/encoding/unicode" 20 "gotest.tools/v3/assert" 21 is "gotest.tools/v3/assert/cmp" 22 "gotest.tools/v3/skip" 23 ) 24 25 func makeConfigFile(t *testing.T, content string) string { 26 t.Helper() 27 name := filepath.Join(t.TempDir(), "daemon.json") 28 err := os.WriteFile(name, []byte(content), 0o666) 29 assert.NilError(t, err) 30 return name 31 } 32 33 func TestDaemonConfigurationNotFound(t *testing.T) { 34 _, err := MergeDaemonConfigurations(&Config{}, nil, "/tmp/foo-bar-baz-docker") 35 assert.Check(t, os.IsNotExist(err), "got: %[1]T: %[1]v", err) 36 } 37 38 func TestDaemonBrokenConfiguration(t *testing.T) { 39 configFile := makeConfigFile(t, `{"Debug": tru`) 40 41 _, err := MergeDaemonConfigurations(&Config{}, nil, configFile) 42 assert.ErrorContains(t, err, `invalid character ' ' in literal true`) 43 } 44 45 // TestDaemonConfigurationUnicodeVariations feeds various variations of Unicode into the JSON parser, ensuring that we 46 // respect a BOM and otherwise default to UTF-8. 47 func TestDaemonConfigurationUnicodeVariations(t *testing.T) { 48 jsonData := `{"debug": true}` 49 50 testCases := []struct { 51 name string 52 encoding encoding.Encoding 53 }{ 54 { 55 name: "UTF-8", 56 encoding: unicode.UTF8, 57 }, 58 { 59 name: "UTF-8 (with BOM)", 60 encoding: unicode.UTF8BOM, 61 }, 62 { 63 name: "UTF-16 (BE with BOM)", 64 encoding: unicode.UTF16(unicode.BigEndian, unicode.UseBOM), 65 }, 66 { 67 name: "UTF-16 (LE with BOM)", 68 encoding: unicode.UTF16(unicode.LittleEndian, unicode.UseBOM), 69 }, 70 } 71 for _, tc := range testCases { 72 t.Run(tc.name, func(t *testing.T) { 73 encodedJson, err := tc.encoding.NewEncoder().String(jsonData) 74 assert.NilError(t, err) 75 configFile := makeConfigFile(t, encodedJson) 76 _, err = MergeDaemonConfigurations(&Config{}, nil, configFile) 77 assert.NilError(t, err) 78 }) 79 } 80 } 81 82 // TestDaemonConfigurationInvalidUnicode ensures that the JSON parser returns a useful error message if malformed UTF-8 83 // is provided. 84 func TestDaemonConfigurationInvalidUnicode(t *testing.T) { 85 configFileBOM := makeConfigFile(t, "\xef\xbb\xbf{\"debug\": true}\xff") 86 _, err := MergeDaemonConfigurations(&Config{}, nil, configFileBOM) 87 assert.ErrorIs(t, err, encoding.ErrInvalidUTF8) 88 89 configFileNoBOM := makeConfigFile(t, "{\"debug\": true}\xff") 90 _, err = MergeDaemonConfigurations(&Config{}, nil, configFileNoBOM) 91 assert.ErrorIs(t, err, encoding.ErrInvalidUTF8) 92 } 93 94 func TestFindConfigurationConflicts(t *testing.T) { 95 config := map[string]interface{}{"authorization-plugins": "foobar"} 96 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 97 98 flags.String("authorization-plugins", "", "") 99 assert.Check(t, flags.Set("authorization-plugins", "asdf")) 100 assert.Check(t, is.ErrorContains(findConfigurationConflicts(config, flags), "authorization-plugins: (from flag: asdf, from file: foobar)")) 101 } 102 103 func TestFindConfigurationConflictsWithNamedOptions(t *testing.T) { 104 config := map[string]interface{}{"hosts": []string{"qwer"}} 105 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 106 107 var hosts []string 108 flags.VarP(opts.NewNamedListOptsRef("hosts", &hosts, opts.ValidateHost), "host", "H", "Daemon socket(s) to connect to") 109 assert.Check(t, flags.Set("host", "tcp://127.0.0.1:4444")) 110 assert.Check(t, flags.Set("host", "unix:///var/run/docker.sock")) 111 assert.Check(t, is.ErrorContains(findConfigurationConflicts(config, flags), "hosts")) 112 } 113 114 func TestDaemonConfigurationMergeConflicts(t *testing.T) { 115 configFile := makeConfigFile(t, `{"debug": true}`) 116 117 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 118 flags.Bool("debug", false, "") 119 assert.Check(t, flags.Set("debug", "false")) 120 121 _, err := MergeDaemonConfigurations(&Config{}, flags, configFile) 122 if err == nil { 123 t.Fatal("expected error, got nil") 124 } 125 if !strings.Contains(err.Error(), "debug") { 126 t.Fatalf("expected debug conflict, got %v", err) 127 } 128 } 129 130 func TestDaemonConfigurationMergeConcurrent(t *testing.T) { 131 configFile := makeConfigFile(t, `{"max-concurrent-downloads": 1}`) 132 133 _, err := MergeDaemonConfigurations(&Config{}, nil, configFile) 134 assert.NilError(t, err) 135 } 136 137 func TestDaemonConfigurationMergeConcurrentError(t *testing.T) { 138 configFile := makeConfigFile(t, `{"max-concurrent-downloads": -1}`) 139 140 _, err := MergeDaemonConfigurations(&Config{}, nil, configFile) 141 assert.ErrorContains(t, err, `invalid max concurrent downloads: -1`) 142 } 143 144 func TestDaemonConfigurationMergeConflictsWithInnerStructs(t *testing.T) { 145 configFile := makeConfigFile(t, `{"tlscacert": "/etc/certificates/ca.pem"}`) 146 147 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 148 flags.String("tlscacert", "", "") 149 assert.Check(t, flags.Set("tlscacert", "~/.docker/ca.pem")) 150 151 _, err := MergeDaemonConfigurations(&Config{}, flags, configFile) 152 assert.ErrorContains(t, err, `the following directives are specified both as a flag and in the configuration file: tlscacert`) 153 } 154 155 // TestDaemonConfigurationMergeDefaultAddressPools is a regression test for #40711. 156 func TestDaemonConfigurationMergeDefaultAddressPools(t *testing.T) { 157 emptyConfigFile := makeConfigFile(t, `{}`) 158 configFile := makeConfigFile(t, `{"default-address-pools":[{"base": "10.123.0.0/16", "size": 24 }]}`) 159 160 expected := []*ipamutils.NetworkToSplit{{Base: "10.123.0.0/16", Size: 24}} 161 162 t.Run("empty config file", func(t *testing.T) { 163 conf := Config{} 164 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 165 flags.Var(&conf.NetworkConfig.DefaultAddressPools, "default-address-pool", "") 166 assert.Check(t, flags.Set("default-address-pool", "base=10.123.0.0/16,size=24")) 167 168 config, err := MergeDaemonConfigurations(&conf, flags, emptyConfigFile) 169 assert.NilError(t, err) 170 assert.DeepEqual(t, config.DefaultAddressPools.Value(), expected) 171 }) 172 173 t.Run("config file", func(t *testing.T) { 174 conf := Config{} 175 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 176 flags.Var(&conf.NetworkConfig.DefaultAddressPools, "default-address-pool", "") 177 178 config, err := MergeDaemonConfigurations(&conf, flags, configFile) 179 assert.NilError(t, err) 180 assert.DeepEqual(t, config.DefaultAddressPools.Value(), expected) 181 }) 182 183 t.Run("with conflicting options", func(t *testing.T) { 184 conf := Config{} 185 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 186 flags.Var(&conf.NetworkConfig.DefaultAddressPools, "default-address-pool", "") 187 assert.Check(t, flags.Set("default-address-pool", "base=10.123.0.0/16,size=24")) 188 189 _, err := MergeDaemonConfigurations(&conf, flags, configFile) 190 assert.ErrorContains(t, err, "the following directives are specified both as a flag and in the configuration file") 191 assert.ErrorContains(t, err, "default-address-pools") 192 }) 193 } 194 195 func TestFindConfigurationConflictsWithUnknownKeys(t *testing.T) { 196 config := map[string]interface{}{"tls-verify": "true"} 197 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 198 199 flags.Bool("tlsverify", false, "") 200 err := findConfigurationConflicts(config, flags) 201 assert.ErrorContains(t, err, "the following directives don't match any configuration option: tls-verify") 202 } 203 204 func TestFindConfigurationConflictsWithMergedValues(t *testing.T) { 205 var hosts []string 206 config := map[string]interface{}{"hosts": "tcp://127.0.0.1:2345"} 207 flags := pflag.NewFlagSet("base", pflag.ContinueOnError) 208 flags.VarP(opts.NewNamedListOptsRef("hosts", &hosts, nil), "host", "H", "") 209 210 err := findConfigurationConflicts(config, flags) 211 assert.NilError(t, err) 212 213 assert.Check(t, flags.Set("host", "unix:///var/run/docker.sock")) 214 err = findConfigurationConflicts(config, flags) 215 assert.ErrorContains(t, err, "hosts: (from flag: [unix:///var/run/docker.sock], from file: tcp://127.0.0.1:2345)") 216 } 217 218 func TestValidateConfigurationErrors(t *testing.T) { 219 testCases := []struct { 220 name string 221 field string 222 config *Config 223 expectedErr string 224 }{ 225 { 226 name: "single label without value", 227 config: &Config{ 228 CommonConfig: CommonConfig{ 229 Labels: []string{"one"}, 230 }, 231 }, 232 expectedErr: "bad attribute format: one", 233 }, 234 { 235 name: "multiple label without value", 236 config: &Config{ 237 CommonConfig: CommonConfig{ 238 Labels: []string{"foo=bar", "one"}, 239 }, 240 }, 241 expectedErr: "bad attribute format: one", 242 }, 243 { 244 name: "single DNSSearch", 245 config: &Config{ 246 CommonConfig: CommonConfig{ 247 DNSConfig: DNSConfig{ 248 DNSSearch: []string{"123456"}, 249 }, 250 }, 251 }, 252 expectedErr: "123456 is not a valid domain", 253 }, 254 { 255 name: "multiple DNSSearch", 256 config: &Config{ 257 CommonConfig: CommonConfig{ 258 DNSConfig: DNSConfig{ 259 DNSSearch: []string{"a.b.c", "123456"}, 260 }, 261 }, 262 }, 263 expectedErr: "123456 is not a valid domain", 264 }, 265 { 266 name: "negative MTU", 267 config: &Config{ 268 CommonConfig: CommonConfig{ 269 BridgeConfig: BridgeConfig{ 270 DefaultBridgeConfig: DefaultBridgeConfig{ 271 MTU: -10, 272 }, 273 }, 274 }, 275 }, 276 expectedErr: "invalid default MTU: -10", 277 }, 278 { 279 name: "negative max-concurrent-downloads", 280 config: &Config{ 281 CommonConfig: CommonConfig{ 282 MaxConcurrentDownloads: -10, 283 }, 284 }, 285 expectedErr: "invalid max concurrent downloads: -10", 286 }, 287 { 288 name: "negative max-concurrent-uploads", 289 config: &Config{ 290 CommonConfig: CommonConfig{ 291 MaxConcurrentUploads: -10, 292 }, 293 }, 294 expectedErr: "invalid max concurrent uploads: -10", 295 }, 296 { 297 name: "negative max-download-attempts", 298 config: &Config{ 299 CommonConfig: CommonConfig{ 300 MaxDownloadAttempts: -10, 301 }, 302 }, 303 expectedErr: "invalid max download attempts: -10", 304 }, 305 // TODO(thaJeztah) temporarily excluding this test as it assumes defaults are set before validating and applying updated configs 306 /* 307 { 308 name: "zero max-download-attempts", 309 field: "MaxDownloadAttempts", 310 config: &Config{ 311 CommonConfig: CommonConfig{ 312 MaxDownloadAttempts: 0, 313 }, 314 }, 315 expectedErr: "invalid max download attempts: 0", 316 }, 317 */ 318 { 319 name: "generic resource without =", 320 config: &Config{ 321 CommonConfig: CommonConfig{ 322 NodeGenericResources: []string{"foo"}, 323 }, 324 }, 325 expectedErr: "could not parse GenericResource: incorrect term foo, missing '=' or malformed expression", 326 }, 327 { 328 name: "generic resource mixed named and discrete", 329 config: &Config{ 330 CommonConfig: CommonConfig{ 331 NodeGenericResources: []string{"foo=bar", "foo=1"}, 332 }, 333 }, 334 expectedErr: "could not parse GenericResource: mixed discrete and named resources in expression 'foo=[bar 1]'", 335 }, 336 { 337 name: "with invalid hosts", 338 config: &Config{ 339 CommonConfig: CommonConfig{ 340 Hosts: []string{"127.0.0.1:2375/path"}, 341 }, 342 }, 343 expectedErr: "invalid bind address (127.0.0.1:2375/path): should not contain a path element", 344 }, 345 { 346 name: "with invalid log-level", 347 config: &Config{ 348 CommonConfig: CommonConfig{ 349 LogLevel: "foobar", 350 }, 351 }, 352 expectedErr: "invalid logging level: foobar", 353 }, 354 } 355 for _, tc := range testCases { 356 t.Run(tc.name, func(t *testing.T) { 357 cfg, err := New() 358 assert.NilError(t, err) 359 if tc.field != "" { 360 assert.Check(t, mergo.Merge(cfg, tc.config, mergo.WithOverride, withForceOverwrite(tc.field))) 361 } else { 362 assert.Check(t, mergo.Merge(cfg, tc.config, mergo.WithOverride)) 363 } 364 err = Validate(cfg) 365 assert.Error(t, err, tc.expectedErr) 366 }) 367 } 368 } 369 370 func withForceOverwrite(fieldName string) func(config *mergo.Config) { 371 return mergo.WithTransformers(overwriteTransformer{fieldName: fieldName}) 372 } 373 374 type overwriteTransformer struct { 375 fieldName string 376 } 377 378 func (tf overwriteTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { 379 if typ == reflect.TypeOf(CommonConfig{}) { 380 return func(dst, src reflect.Value) error { 381 dst.FieldByName(tf.fieldName).Set(src.FieldByName(tf.fieldName)) 382 return nil 383 } 384 } 385 return nil 386 } 387 388 func TestValidateConfiguration(t *testing.T) { 389 testCases := []struct { 390 name string 391 field string 392 config *Config 393 }{ 394 { 395 name: "with label", 396 field: "Labels", 397 config: &Config{ 398 CommonConfig: CommonConfig{ 399 Labels: []string{"one=two"}, 400 }, 401 }, 402 }, 403 { 404 name: "with dns-search", 405 field: "DNSConfig", 406 config: &Config{ 407 CommonConfig: CommonConfig{ 408 DNSConfig: DNSConfig{ 409 DNSSearch: []string{"a.b.c"}, 410 }, 411 }, 412 }, 413 }, 414 { 415 name: "with mtu", 416 field: "MTU", 417 config: &Config{ 418 CommonConfig: CommonConfig{ 419 BridgeConfig: BridgeConfig{ 420 DefaultBridgeConfig: DefaultBridgeConfig{ 421 MTU: 1234, 422 }, 423 }, 424 }, 425 }, 426 }, 427 { 428 name: "with max-concurrent-downloads", 429 field: "MaxConcurrentDownloads", 430 config: &Config{ 431 CommonConfig: CommonConfig{ 432 MaxConcurrentDownloads: 4, 433 }, 434 }, 435 }, 436 { 437 name: "with max-concurrent-uploads", 438 field: "MaxConcurrentUploads", 439 config: &Config{ 440 CommonConfig: CommonConfig{ 441 MaxConcurrentUploads: 4, 442 }, 443 }, 444 }, 445 { 446 name: "with max-download-attempts", 447 field: "MaxDownloadAttempts", 448 config: &Config{ 449 CommonConfig: CommonConfig{ 450 MaxDownloadAttempts: 4, 451 }, 452 }, 453 }, 454 { 455 name: "with multiple node generic resources", 456 field: "NodeGenericResources", 457 config: &Config{ 458 CommonConfig: CommonConfig{ 459 NodeGenericResources: []string{"foo=bar", "foo=baz"}, 460 }, 461 }, 462 }, 463 { 464 name: "with node generic resources", 465 field: "NodeGenericResources", 466 config: &Config{ 467 CommonConfig: CommonConfig{ 468 NodeGenericResources: []string{"foo=1"}, 469 }, 470 }, 471 }, 472 { 473 name: "with hosts", 474 field: "Hosts", 475 config: &Config{ 476 CommonConfig: CommonConfig{ 477 Hosts: []string{"tcp://127.0.0.1:2375"}, 478 }, 479 }, 480 }, 481 { 482 name: "with log-level warn", 483 field: "LogLevel", 484 config: &Config{ 485 CommonConfig: CommonConfig{ 486 LogLevel: "warn", 487 }, 488 }, 489 }, 490 } 491 for _, tc := range testCases { 492 t.Run(tc.name, func(t *testing.T) { 493 // Start with a config with all defaults set, so that we only 494 cfg, err := New() 495 assert.NilError(t, err) 496 assert.Check(t, mergo.Merge(cfg, tc.config, mergo.WithOverride)) 497 498 // Check that the override happened :) 499 assert.Check(t, is.DeepEqual(cfg, tc.config, field(tc.field))) 500 err = Validate(cfg) 501 assert.NilError(t, err) 502 }) 503 } 504 } 505 506 func TestValidateMinAPIVersion(t *testing.T) { 507 t.Parallel() 508 tests := []struct { 509 doc string 510 input string 511 expectedErr string 512 }{ 513 { 514 doc: "empty", 515 expectedErr: "value is empty", 516 }, 517 { 518 doc: "with prefix", 519 input: "v1.43", 520 expectedErr: `API version must be provided without "v" prefix`, 521 }, 522 { 523 doc: "major only", 524 input: "1", 525 expectedErr: `minimum supported API version is`, 526 }, 527 { 528 doc: "too low", 529 input: "1.0", 530 expectedErr: `minimum supported API version is`, 531 }, 532 { 533 doc: "minor too high", 534 input: "1.99", 535 expectedErr: `maximum supported API version is`, 536 }, 537 { 538 doc: "major too high", 539 input: "9.0", 540 expectedErr: `maximum supported API version is`, 541 }, 542 { 543 doc: "current version", 544 input: api.DefaultVersion, 545 }, 546 } 547 548 for _, tc := range tests { 549 tc := tc 550 t.Run(tc.doc, func(t *testing.T) { 551 err := ValidateMinAPIVersion(tc.input) 552 if tc.expectedErr != "" { 553 assert.Check(t, is.ErrorContains(err, tc.expectedErr)) 554 } else { 555 assert.Check(t, err) 556 } 557 }) 558 } 559 560 } 561 562 func TestConfigInvalidDNS(t *testing.T) { 563 tests := []struct { 564 doc string 565 input string 566 expectedErr string 567 }{ 568 { 569 doc: "single DNS, invalid IP-address", 570 input: `{"dns": ["1.1.1.1o"]}`, 571 expectedErr: `invalid IP address: 1.1.1.1o`, 572 }, 573 { 574 doc: "multiple DNS, invalid IP-address", 575 input: `{"dns": ["2.2.2.2", "1.1.1.1o"]}`, 576 expectedErr: `invalid IP address: 1.1.1.1o`, 577 }, 578 } 579 580 for _, tc := range tests { 581 tc := tc 582 t.Run(tc.doc, func(t *testing.T) { 583 var cfg Config 584 err := json.Unmarshal([]byte(tc.input), &cfg) 585 assert.Check(t, is.Error(err, tc.expectedErr)) 586 }) 587 } 588 } 589 590 func field(field string) cmp.Option { 591 tmp := reflect.TypeOf(Config{}) 592 ignoreFields := make([]string, 0, tmp.NumField()) 593 for i := 0; i < tmp.NumField(); i++ { 594 if tmp.Field(i).Name != field { 595 ignoreFields = append(ignoreFields, tmp.Field(i).Name) 596 } 597 } 598 return cmpopts.IgnoreFields(Config{}, ignoreFields...) 599 } 600 601 // TestReloadSetConfigFileNotExist tests that when `--config-file` is set, and it doesn't exist the `Reload` function 602 // returns an error. 603 func TestReloadSetConfigFileNotExist(t *testing.T) { 604 configFile := "/tmp/blabla/not/exists/config.json" 605 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 606 flags.String("config-file", "", "") 607 assert.Check(t, flags.Set("config-file", configFile)) 608 609 err := Reload(configFile, flags, func(c *Config) {}) 610 assert.Check(t, is.ErrorContains(err, "unable to configure the Docker daemon with file")) 611 } 612 613 // TestReloadDefaultConfigNotExist tests that if the default configuration file doesn't exist the daemon still will 614 // still be reloaded. 615 func TestReloadDefaultConfigNotExist(t *testing.T) { 616 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 617 defaultConfigFile := "/tmp/blabla/not/exists/daemon.json" 618 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 619 flags.String("config-file", defaultConfigFile, "") 620 reloaded := false 621 err := Reload(defaultConfigFile, flags, func(c *Config) { 622 reloaded = true 623 }) 624 assert.Check(t, err) 625 assert.Check(t, reloaded) 626 } 627 628 // TestReloadBadDefaultConfig tests that when `--config-file` is not set and the default configuration file exists and 629 // is bad, an error is returned. 630 func TestReloadBadDefaultConfig(t *testing.T) { 631 configFile := makeConfigFile(t, `{wrong: "configuration"}`) 632 633 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 634 flags.String("config-file", configFile, "") 635 reloaded := false 636 err := Reload(configFile, flags, func(c *Config) { 637 reloaded = true 638 }) 639 assert.Check(t, is.ErrorContains(err, "unable to configure the Docker daemon with file")) 640 assert.Check(t, reloaded == false) 641 } 642 643 func TestReloadWithConflictingLabels(t *testing.T) { 644 configFile := makeConfigFile(t, `{"labels": ["foo=bar", "foo=baz"]}`) 645 646 var lbls []string 647 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 648 flags.String("config-file", configFile, "") 649 flags.StringSlice("labels", lbls, "") 650 reloaded := false 651 err := Reload(configFile, flags, func(c *Config) { 652 reloaded = true 653 }) 654 assert.Check(t, is.ErrorContains(err, "conflict labels for foo=baz and foo=bar")) 655 assert.Check(t, reloaded == false) 656 } 657 658 func TestReloadWithDuplicateLabels(t *testing.T) { 659 configFile := makeConfigFile(t, `{"labels": ["foo=the-same", "foo=the-same"]}`) 660 661 var lbls []string 662 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 663 flags.String("config-file", configFile, "") 664 flags.StringSlice("labels", lbls, "") 665 reloaded := false 666 err := Reload(configFile, flags, func(c *Config) { 667 reloaded = true 668 assert.Check(t, is.DeepEqual(c.Labels, []string{"foo=the-same"})) 669 }) 670 assert.Check(t, err) 671 assert.Check(t, reloaded) 672 } 673 674 func TestMaskURLCredentials(t *testing.T) { 675 tests := []struct { 676 rawURL string 677 maskedURL string 678 }{ 679 { 680 rawURL: "", 681 maskedURL: "", 682 }, { 683 rawURL: "invalidURL", 684 maskedURL: "invalidURL", 685 }, { 686 rawURL: "http://proxy.example.com:80/", 687 maskedURL: "http://proxy.example.com:80/", 688 }, { 689 rawURL: "http://USER:PASSWORD@proxy.example.com:80/", 690 maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/", 691 }, { 692 rawURL: "http://PASSWORD:PASSWORD@proxy.example.com:80/", 693 maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/", 694 }, { 695 rawURL: "http://USER:@proxy.example.com:80/", 696 maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/", 697 }, { 698 rawURL: "http://:PASSWORD@proxy.example.com:80/", 699 maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/", 700 }, { 701 rawURL: "http://USER@docker:password@proxy.example.com:80/", 702 maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/", 703 }, { 704 rawURL: "http://USER%40docker:password@proxy.example.com:80/", 705 maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/", 706 }, { 707 rawURL: "http://USER%40docker:pa%3Fsword@proxy.example.com:80/", 708 maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/", 709 }, { 710 rawURL: "http://USER%40docker:pa%3Fsword@proxy.example.com:80/hello%20world", 711 maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/hello%20world", 712 }, 713 } 714 for _, test := range tests { 715 maskedURL := MaskCredentials(test.rawURL) 716 assert.Equal(t, maskedURL, test.maskedURL) 717 } 718 }