github.com/afbjorklund/moby@v20.10.5+incompatible/daemon/config/config_test.go (about) 1 package config // import "github.com/docker/docker/daemon/config" 2 3 import ( 4 "io/ioutil" 5 "os" 6 "strings" 7 "testing" 8 9 "github.com/docker/docker/daemon/discovery" 10 "github.com/docker/docker/opts" 11 "github.com/docker/libnetwork/ipamutils" 12 "github.com/spf13/pflag" 13 "gotest.tools/v3/assert" 14 is "gotest.tools/v3/assert/cmp" 15 "gotest.tools/v3/fs" 16 "gotest.tools/v3/skip" 17 ) 18 19 func TestDaemonConfigurationNotFound(t *testing.T) { 20 _, err := MergeDaemonConfigurations(&Config{}, nil, "/tmp/foo-bar-baz-docker") 21 if err == nil || !os.IsNotExist(err) { 22 t.Fatalf("expected does not exist error, got %v", err) 23 } 24 } 25 26 func TestDaemonBrokenConfiguration(t *testing.T) { 27 f, err := ioutil.TempFile("", "docker-config-") 28 if err != nil { 29 t.Fatal(err) 30 } 31 32 configFile := f.Name() 33 f.Write([]byte(`{"Debug": tru`)) 34 f.Close() 35 36 _, err = MergeDaemonConfigurations(&Config{}, nil, configFile) 37 if err == nil { 38 t.Fatalf("expected error, got %v", err) 39 } 40 } 41 42 func TestParseClusterAdvertiseSettings(t *testing.T) { 43 _, err := ParseClusterAdvertiseSettings("something", "") 44 if err != discovery.ErrDiscoveryDisabled { 45 t.Fatalf("expected discovery disabled error, got %v\n", err) 46 } 47 48 _, err = ParseClusterAdvertiseSettings("", "something") 49 if err == nil { 50 t.Fatalf("expected discovery store error, got %v\n", err) 51 } 52 53 _, err = ParseClusterAdvertiseSettings("etcd", "127.0.0.1:8080") 54 if err != nil { 55 t.Fatal(err) 56 } 57 } 58 59 func TestFindConfigurationConflicts(t *testing.T) { 60 config := map[string]interface{}{"authorization-plugins": "foobar"} 61 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 62 63 flags.String("authorization-plugins", "", "") 64 assert.Check(t, flags.Set("authorization-plugins", "asdf")) 65 assert.Check(t, is.ErrorContains(findConfigurationConflicts(config, flags), "authorization-plugins: (from flag: asdf, from file: foobar)")) 66 } 67 68 func TestFindConfigurationConflictsWithNamedOptions(t *testing.T) { 69 config := map[string]interface{}{"hosts": []string{"qwer"}} 70 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 71 72 var hosts []string 73 flags.VarP(opts.NewNamedListOptsRef("hosts", &hosts, opts.ValidateHost), "host", "H", "Daemon socket(s) to connect to") 74 assert.Check(t, flags.Set("host", "tcp://127.0.0.1:4444")) 75 assert.Check(t, flags.Set("host", "unix:///var/run/docker.sock")) 76 assert.Check(t, is.ErrorContains(findConfigurationConflicts(config, flags), "hosts")) 77 } 78 79 func TestDaemonConfigurationMergeConflicts(t *testing.T) { 80 f, err := ioutil.TempFile("", "docker-config-") 81 if err != nil { 82 t.Fatal(err) 83 } 84 85 configFile := f.Name() 86 f.Write([]byte(`{"debug": true}`)) 87 f.Close() 88 89 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 90 flags.Bool("debug", false, "") 91 flags.Set("debug", "false") 92 93 _, err = MergeDaemonConfigurations(&Config{}, flags, configFile) 94 if err == nil { 95 t.Fatal("expected error, got nil") 96 } 97 if !strings.Contains(err.Error(), "debug") { 98 t.Fatalf("expected debug conflict, got %v", err) 99 } 100 } 101 102 func TestDaemonConfigurationMergeConcurrent(t *testing.T) { 103 f, err := ioutil.TempFile("", "docker-config-") 104 if err != nil { 105 t.Fatal(err) 106 } 107 108 configFile := f.Name() 109 f.Write([]byte(`{"max-concurrent-downloads": 1}`)) 110 f.Close() 111 112 _, err = MergeDaemonConfigurations(&Config{}, nil, configFile) 113 if err != nil { 114 t.Fatal("expected error, got nil") 115 } 116 } 117 118 func TestDaemonConfigurationMergeConcurrentError(t *testing.T) { 119 f, err := ioutil.TempFile("", "docker-config-") 120 if err != nil { 121 t.Fatal(err) 122 } 123 124 configFile := f.Name() 125 f.Write([]byte(`{"max-concurrent-downloads": -1}`)) 126 f.Close() 127 128 _, err = MergeDaemonConfigurations(&Config{}, nil, configFile) 129 if err == nil { 130 t.Fatalf("expected no error, got error %v", err) 131 } 132 } 133 134 func TestDaemonConfigurationMergeConflictsWithInnerStructs(t *testing.T) { 135 f, err := ioutil.TempFile("", "docker-config-") 136 if err != nil { 137 t.Fatal(err) 138 } 139 140 configFile := f.Name() 141 f.Write([]byte(`{"tlscacert": "/etc/certificates/ca.pem"}`)) 142 f.Close() 143 144 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 145 flags.String("tlscacert", "", "") 146 flags.Set("tlscacert", "~/.docker/ca.pem") 147 148 _, err = MergeDaemonConfigurations(&Config{}, flags, configFile) 149 if err == nil { 150 t.Fatal("expected error, got nil") 151 } 152 if !strings.Contains(err.Error(), "tlscacert") { 153 t.Fatalf("expected tlscacert conflict, got %v", err) 154 } 155 } 156 157 // Test for #40711 158 func TestDaemonConfigurationMergeDefaultAddressPools(t *testing.T) { 159 emptyConfigFile := fs.NewFile(t, "config", fs.WithContent(`{}`)) 160 defer emptyConfigFile.Remove() 161 configFile := fs.NewFile(t, "config", fs.WithContent(`{"default-address-pools":[{"base": "10.123.0.0/16", "size": 24 }]}`)) 162 defer configFile.Remove() 163 164 expected := []*ipamutils.NetworkToSplit{{Base: "10.123.0.0/16", Size: 24}} 165 166 t.Run("empty config file", func(t *testing.T) { 167 var conf = Config{} 168 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 169 flags.Var(&conf.NetworkConfig.DefaultAddressPools, "default-address-pool", "") 170 flags.Set("default-address-pool", "base=10.123.0.0/16,size=24") 171 172 config, err := MergeDaemonConfigurations(&conf, flags, emptyConfigFile.Path()) 173 assert.NilError(t, err) 174 assert.DeepEqual(t, config.DefaultAddressPools.Value(), expected) 175 }) 176 177 t.Run("config file", func(t *testing.T) { 178 var conf = Config{} 179 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 180 flags.Var(&conf.NetworkConfig.DefaultAddressPools, "default-address-pool", "") 181 182 config, err := MergeDaemonConfigurations(&conf, flags, configFile.Path()) 183 assert.NilError(t, err) 184 assert.DeepEqual(t, config.DefaultAddressPools.Value(), expected) 185 }) 186 187 t.Run("with conflicting options", func(t *testing.T) { 188 var conf = Config{} 189 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 190 flags.Var(&conf.NetworkConfig.DefaultAddressPools, "default-address-pool", "") 191 flags.Set("default-address-pool", "base=10.123.0.0/16,size=24") 192 193 _, err := MergeDaemonConfigurations(&conf, flags, configFile.Path()) 194 assert.ErrorContains(t, err, "the following directives are specified both as a flag and in the configuration file") 195 assert.ErrorContains(t, err, "default-address-pools") 196 }) 197 } 198 199 func TestFindConfigurationConflictsWithUnknownKeys(t *testing.T) { 200 config := map[string]interface{}{"tls-verify": "true"} 201 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 202 203 flags.Bool("tlsverify", false, "") 204 err := findConfigurationConflicts(config, flags) 205 if err == nil { 206 t.Fatal("expected error, got nil") 207 } 208 if !strings.Contains(err.Error(), "the following directives don't match any configuration option: tls-verify") { 209 t.Fatalf("expected tls-verify conflict, got %v", err) 210 } 211 } 212 213 func TestFindConfigurationConflictsWithMergedValues(t *testing.T) { 214 var hosts []string 215 config := map[string]interface{}{"hosts": "tcp://127.0.0.1:2345"} 216 flags := pflag.NewFlagSet("base", pflag.ContinueOnError) 217 flags.VarP(opts.NewNamedListOptsRef("hosts", &hosts, nil), "host", "H", "") 218 219 err := findConfigurationConflicts(config, flags) 220 if err != nil { 221 t.Fatal(err) 222 } 223 224 flags.Set("host", "unix:///var/run/docker.sock") 225 err = findConfigurationConflicts(config, flags) 226 if err == nil { 227 t.Fatal("expected error, got nil") 228 } 229 if !strings.Contains(err.Error(), "hosts: (from flag: [unix:///var/run/docker.sock], from file: tcp://127.0.0.1:2345)") { 230 t.Fatalf("expected hosts conflict, got %v", err) 231 } 232 } 233 234 func TestValidateConfigurationErrors(t *testing.T) { 235 intPtr := func(i int) *int { return &i } 236 237 testCases := []struct { 238 name string 239 config *Config 240 expectedErr string 241 }{ 242 { 243 name: "single label without value", 244 config: &Config{ 245 CommonConfig: CommonConfig{ 246 Labels: []string{"one"}, 247 }, 248 }, 249 expectedErr: "bad attribute format: one", 250 }, 251 { 252 name: "multiple label without value", 253 config: &Config{ 254 CommonConfig: CommonConfig{ 255 Labels: []string{"foo=bar", "one"}, 256 }, 257 }, 258 expectedErr: "bad attribute format: one", 259 }, 260 { 261 name: "single DNS, invalid IP-address", 262 config: &Config{ 263 CommonConfig: CommonConfig{ 264 DNSConfig: DNSConfig{ 265 DNS: []string{"1.1.1.1o"}, 266 }, 267 }, 268 }, 269 expectedErr: "1.1.1.1o is not an ip address", 270 }, 271 { 272 name: "multiple DNS, invalid IP-address", 273 config: &Config{ 274 CommonConfig: CommonConfig{ 275 DNSConfig: DNSConfig{ 276 DNS: []string{"2.2.2.2", "1.1.1.1o"}, 277 }, 278 }, 279 }, 280 expectedErr: "1.1.1.1o is not an ip address", 281 }, 282 { 283 name: "single DNSSearch", 284 config: &Config{ 285 CommonConfig: CommonConfig{ 286 DNSConfig: DNSConfig{ 287 DNSSearch: []string{"123456"}, 288 }, 289 }, 290 }, 291 expectedErr: "123456 is not a valid domain", 292 }, 293 { 294 name: "multiple DNSSearch", 295 config: &Config{ 296 CommonConfig: CommonConfig{ 297 DNSConfig: DNSConfig{ 298 DNSSearch: []string{"a.b.c", "123456"}, 299 }, 300 }, 301 }, 302 expectedErr: "123456 is not a valid domain", 303 }, 304 { 305 name: "negative max-concurrent-downloads", 306 config: &Config{ 307 CommonConfig: CommonConfig{ 308 MaxConcurrentDownloads: intPtr(-10), 309 }, 310 }, 311 expectedErr: "invalid max concurrent downloads: -10", 312 }, 313 { 314 name: "negative max-concurrent-uploads", 315 config: &Config{ 316 CommonConfig: CommonConfig{ 317 MaxConcurrentUploads: intPtr(-10), 318 }, 319 }, 320 expectedErr: "invalid max concurrent uploads: -10", 321 }, 322 { 323 name: "negative max-download-attempts", 324 config: &Config{ 325 CommonConfig: CommonConfig{ 326 MaxDownloadAttempts: intPtr(-10), 327 }, 328 }, 329 expectedErr: "invalid max download attempts: -10", 330 }, 331 { 332 name: "zero max-download-attempts", 333 config: &Config{ 334 CommonConfig: CommonConfig{ 335 MaxDownloadAttempts: intPtr(0), 336 }, 337 }, 338 expectedErr: "invalid max download attempts: 0", 339 }, 340 { 341 name: "generic resource without =", 342 config: &Config{ 343 CommonConfig: CommonConfig{ 344 NodeGenericResources: []string{"foo"}, 345 }, 346 }, 347 expectedErr: "could not parse GenericResource: incorrect term foo, missing '=' or malformed expression", 348 }, 349 { 350 name: "generic resource mixed named and discrete", 351 config: &Config{ 352 CommonConfig: CommonConfig{ 353 NodeGenericResources: []string{"foo=bar", "foo=1"}, 354 }, 355 }, 356 expectedErr: "could not parse GenericResource: mixed discrete and named resources in expression 'foo=[bar 1]'", 357 }, 358 } 359 for _, tc := range testCases { 360 t.Run(tc.name, func(t *testing.T) { 361 err := Validate(tc.config) 362 assert.Error(t, err, tc.expectedErr) 363 }) 364 } 365 } 366 367 func TestValidateConfiguration(t *testing.T) { 368 intPtr := func(i int) *int { return &i } 369 370 testCases := []struct { 371 name string 372 config *Config 373 }{ 374 { 375 name: "with label", 376 config: &Config{ 377 CommonConfig: CommonConfig{ 378 Labels: []string{"one=two"}, 379 }, 380 }, 381 }, 382 { 383 name: "with dns", 384 config: &Config{ 385 CommonConfig: CommonConfig{ 386 DNSConfig: DNSConfig{ 387 DNS: []string{"1.1.1.1"}, 388 }, 389 }, 390 }, 391 }, 392 { 393 name: "with dns-search", 394 config: &Config{ 395 CommonConfig: CommonConfig{ 396 DNSConfig: DNSConfig{ 397 DNSSearch: []string{"a.b.c"}, 398 }, 399 }, 400 }, 401 }, 402 { 403 name: "with max-concurrent-downloads", 404 config: &Config{ 405 CommonConfig: CommonConfig{ 406 MaxConcurrentDownloads: intPtr(4), 407 }, 408 }, 409 }, 410 { 411 name: "with max-concurrent-uploads", 412 config: &Config{ 413 CommonConfig: CommonConfig{ 414 MaxConcurrentUploads: intPtr(4), 415 }, 416 }, 417 }, 418 { 419 name: "with max-download-attempts", 420 config: &Config{ 421 CommonConfig: CommonConfig{ 422 MaxDownloadAttempts: intPtr(4), 423 }, 424 }, 425 }, 426 { 427 name: "with multiple node generic resources", 428 config: &Config{ 429 CommonConfig: CommonConfig{ 430 NodeGenericResources: []string{"foo=bar", "foo=baz"}, 431 }, 432 }, 433 }, 434 { 435 name: "with node generic resources", 436 config: &Config{ 437 CommonConfig: CommonConfig{ 438 NodeGenericResources: []string{"foo=1"}, 439 }, 440 }, 441 }, 442 } 443 for _, tc := range testCases { 444 t.Run(tc.name, func(t *testing.T) { 445 err := Validate(tc.config) 446 assert.NilError(t, err) 447 }) 448 } 449 } 450 451 func TestModifiedDiscoverySettings(t *testing.T) { 452 cases := []struct { 453 current *Config 454 modified *Config 455 expected bool 456 }{ 457 { 458 current: discoveryConfig("foo", "bar", map[string]string{}), 459 modified: discoveryConfig("foo", "bar", map[string]string{}), 460 expected: false, 461 }, 462 { 463 current: discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}), 464 modified: discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}), 465 expected: false, 466 }, 467 { 468 current: discoveryConfig("foo", "bar", map[string]string{}), 469 modified: discoveryConfig("foo", "bar", nil), 470 expected: false, 471 }, 472 { 473 current: discoveryConfig("foo", "bar", nil), 474 modified: discoveryConfig("foo", "bar", map[string]string{}), 475 expected: false, 476 }, 477 { 478 current: discoveryConfig("foo", "bar", nil), 479 modified: discoveryConfig("baz", "bar", nil), 480 expected: true, 481 }, 482 { 483 current: discoveryConfig("foo", "bar", nil), 484 modified: discoveryConfig("foo", "baz", nil), 485 expected: true, 486 }, 487 { 488 current: discoveryConfig("foo", "bar", nil), 489 modified: discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}), 490 expected: true, 491 }, 492 } 493 494 for _, c := range cases { 495 got := ModifiedDiscoverySettings(c.current, c.modified.ClusterStore, c.modified.ClusterAdvertise, c.modified.ClusterOpts) 496 if c.expected != got { 497 t.Fatalf("expected %v, got %v: current config %v, new config %v", c.expected, got, c.current, c.modified) 498 } 499 } 500 } 501 502 func discoveryConfig(backendAddr, advertiseAddr string, opts map[string]string) *Config { 503 return &Config{ 504 CommonConfig: CommonConfig{ 505 ClusterStore: backendAddr, 506 ClusterAdvertise: advertiseAddr, 507 ClusterOpts: opts, 508 }, 509 } 510 } 511 512 // TestReloadSetConfigFileNotExist tests that when `--config-file` is set 513 // and it doesn't exist the `Reload` function returns an error. 514 func TestReloadSetConfigFileNotExist(t *testing.T) { 515 configFile := "/tmp/blabla/not/exists/config.json" 516 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 517 flags.String("config-file", "", "") 518 flags.Set("config-file", configFile) 519 520 err := Reload(configFile, flags, func(c *Config) {}) 521 assert.Check(t, is.ErrorContains(err, "unable to configure the Docker daemon with file")) 522 } 523 524 // TestReloadDefaultConfigNotExist tests that if the default configuration file 525 // doesn't exist the daemon still will be reloaded. 526 func TestReloadDefaultConfigNotExist(t *testing.T) { 527 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 528 reloaded := false 529 configFile := "/etc/docker/daemon.json" 530 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 531 flags.String("config-file", configFile, "") 532 err := Reload(configFile, flags, func(c *Config) { 533 reloaded = true 534 }) 535 assert.Check(t, err) 536 assert.Check(t, reloaded) 537 } 538 539 // TestReloadBadDefaultConfig tests that when `--config-file` is not set 540 // and the default configuration file exists and is bad return an error 541 func TestReloadBadDefaultConfig(t *testing.T) { 542 f, err := ioutil.TempFile("", "docker-config-") 543 if err != nil { 544 t.Fatal(err) 545 } 546 547 configFile := f.Name() 548 f.Write([]byte(`{wrong: "configuration"}`)) 549 f.Close() 550 551 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 552 flags.String("config-file", configFile, "") 553 err = Reload(configFile, flags, func(c *Config) {}) 554 assert.Check(t, is.ErrorContains(err, "unable to configure the Docker daemon with file")) 555 } 556 557 func TestReloadWithConflictingLabels(t *testing.T) { 558 tempFile := fs.NewFile(t, "config", fs.WithContent(`{"labels":["foo=bar","foo=baz"]}`)) 559 defer tempFile.Remove() 560 configFile := tempFile.Path() 561 562 var lbls []string 563 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 564 flags.String("config-file", configFile, "") 565 flags.StringSlice("labels", lbls, "") 566 err := Reload(configFile, flags, func(c *Config) {}) 567 assert.Check(t, is.ErrorContains(err, "conflict labels for foo=baz and foo=bar")) 568 } 569 570 func TestReloadWithDuplicateLabels(t *testing.T) { 571 tempFile := fs.NewFile(t, "config", fs.WithContent(`{"labels":["foo=the-same","foo=the-same"]}`)) 572 defer tempFile.Remove() 573 configFile := tempFile.Path() 574 575 var lbls []string 576 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 577 flags.String("config-file", configFile, "") 578 flags.StringSlice("labels", lbls, "") 579 err := Reload(configFile, flags, func(c *Config) {}) 580 assert.Check(t, err) 581 }