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