github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/daemon/config/config_test.go (about) 1 package config // import "github.com/demonoid81/moby/daemon/config" 2 3 import ( 4 "io/ioutil" 5 "os" 6 "strings" 7 "testing" 8 9 "github.com/demonoid81/moby/daemon/discovery" 10 "github.com/demonoid81/moby/opts" 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 := ioutil.TempFile("", "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 := ioutil.TempFile("", "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 := ioutil.TempFile("", "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 := ioutil.TempFile("", "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 := ioutil.TempFile("", "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 func TestFindConfigurationConflictsWithUnknownKeys(t *testing.T) { 157 config := map[string]interface{}{"tls-verify": "true"} 158 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 159 160 flags.Bool("tlsverify", false, "") 161 err := findConfigurationConflicts(config, flags) 162 if err == nil { 163 t.Fatal("expected error, got nil") 164 } 165 if !strings.Contains(err.Error(), "the following directives don't match any configuration option: tls-verify") { 166 t.Fatalf("expected tls-verify conflict, got %v", err) 167 } 168 } 169 170 func TestFindConfigurationConflictsWithMergedValues(t *testing.T) { 171 var hosts []string 172 config := map[string]interface{}{"hosts": "tcp://127.0.0.1:2345"} 173 flags := pflag.NewFlagSet("base", pflag.ContinueOnError) 174 flags.VarP(opts.NewNamedListOptsRef("hosts", &hosts, nil), "host", "H", "") 175 176 err := findConfigurationConflicts(config, flags) 177 if err != nil { 178 t.Fatal(err) 179 } 180 181 flags.Set("host", "unix:///var/run/docker.sock") 182 err = findConfigurationConflicts(config, flags) 183 if err == nil { 184 t.Fatal("expected error, got nil") 185 } 186 if !strings.Contains(err.Error(), "hosts: (from flag: [unix:///var/run/docker.sock], from file: tcp://127.0.0.1:2345)") { 187 t.Fatalf("expected hosts conflict, got %v", err) 188 } 189 } 190 191 func TestValidateConfigurationErrors(t *testing.T) { 192 intPtr := func(i int) *int { return &i } 193 194 testCases := []struct { 195 name string 196 config *Config 197 expectedErr string 198 }{ 199 { 200 name: "single label without value", 201 config: &Config{ 202 CommonConfig: CommonConfig{ 203 Labels: []string{"one"}, 204 }, 205 }, 206 expectedErr: "bad attribute format: one", 207 }, 208 { 209 name: "multiple label without value", 210 config: &Config{ 211 CommonConfig: CommonConfig{ 212 Labels: []string{"foo=bar", "one"}, 213 }, 214 }, 215 expectedErr: "bad attribute format: one", 216 }, 217 { 218 name: "single DNS, invalid IP-address", 219 config: &Config{ 220 CommonConfig: CommonConfig{ 221 DNSConfig: DNSConfig{ 222 DNS: []string{"1.1.1.1o"}, 223 }, 224 }, 225 }, 226 expectedErr: "1.1.1.1o is not an ip address", 227 }, 228 { 229 name: "multiple DNS, invalid IP-address", 230 config: &Config{ 231 CommonConfig: CommonConfig{ 232 DNSConfig: DNSConfig{ 233 DNS: []string{"2.2.2.2", "1.1.1.1o"}, 234 }, 235 }, 236 }, 237 expectedErr: "1.1.1.1o is not an ip address", 238 }, 239 { 240 name: "single DNSSearch", 241 config: &Config{ 242 CommonConfig: CommonConfig{ 243 DNSConfig: DNSConfig{ 244 DNSSearch: []string{"123456"}, 245 }, 246 }, 247 }, 248 expectedErr: "123456 is not a valid domain", 249 }, 250 { 251 name: "multiple DNSSearch", 252 config: &Config{ 253 CommonConfig: CommonConfig{ 254 DNSConfig: DNSConfig{ 255 DNSSearch: []string{"a.b.c", "123456"}, 256 }, 257 }, 258 }, 259 expectedErr: "123456 is not a valid domain", 260 }, 261 { 262 name: "negative max-concurrent-downloads", 263 config: &Config{ 264 CommonConfig: CommonConfig{ 265 MaxConcurrentDownloads: intPtr(-10), 266 }, 267 }, 268 expectedErr: "invalid max concurrent downloads: -10", 269 }, 270 { 271 name: "negative max-concurrent-uploads", 272 config: &Config{ 273 CommonConfig: CommonConfig{ 274 MaxConcurrentUploads: intPtr(-10), 275 }, 276 }, 277 expectedErr: "invalid max concurrent uploads: -10", 278 }, 279 { 280 name: "negative max-download-attempts", 281 config: &Config{ 282 CommonConfig: CommonConfig{ 283 MaxDownloadAttempts: intPtr(-10), 284 }, 285 }, 286 expectedErr: "invalid max download attempts: -10", 287 }, 288 { 289 name: "zero max-download-attempts", 290 config: &Config{ 291 CommonConfig: CommonConfig{ 292 MaxDownloadAttempts: intPtr(0), 293 }, 294 }, 295 expectedErr: "invalid max download attempts: 0", 296 }, 297 { 298 name: "generic resource without =", 299 config: &Config{ 300 CommonConfig: CommonConfig{ 301 NodeGenericResources: []string{"foo"}, 302 }, 303 }, 304 expectedErr: "could not parse GenericResource: incorrect term foo, missing '=' or malformed expression", 305 }, 306 { 307 name: "generic resource mixed named and discrete", 308 config: &Config{ 309 CommonConfig: CommonConfig{ 310 NodeGenericResources: []string{"foo=bar", "foo=1"}, 311 }, 312 }, 313 expectedErr: "could not parse GenericResource: mixed discrete and named resources in expression 'foo=[bar 1]'", 314 }, 315 } 316 for _, tc := range testCases { 317 t.Run(tc.name, func(t *testing.T) { 318 err := Validate(tc.config) 319 assert.Error(t, err, tc.expectedErr) 320 }) 321 } 322 } 323 324 func TestValidateConfiguration(t *testing.T) { 325 intPtr := func(i int) *int { return &i } 326 327 testCases := []struct { 328 name string 329 config *Config 330 }{ 331 { 332 name: "with label", 333 config: &Config{ 334 CommonConfig: CommonConfig{ 335 Labels: []string{"one=two"}, 336 }, 337 }, 338 }, 339 { 340 name: "with dns", 341 config: &Config{ 342 CommonConfig: CommonConfig{ 343 DNSConfig: DNSConfig{ 344 DNS: []string{"1.1.1.1"}, 345 }, 346 }, 347 }, 348 }, 349 { 350 name: "with dns-search", 351 config: &Config{ 352 CommonConfig: CommonConfig{ 353 DNSConfig: DNSConfig{ 354 DNSSearch: []string{"a.b.c"}, 355 }, 356 }, 357 }, 358 }, 359 { 360 name: "with max-concurrent-downloads", 361 config: &Config{ 362 CommonConfig: CommonConfig{ 363 MaxConcurrentDownloads: intPtr(4), 364 }, 365 }, 366 }, 367 { 368 name: "with max-concurrent-uploads", 369 config: &Config{ 370 CommonConfig: CommonConfig{ 371 MaxConcurrentUploads: intPtr(4), 372 }, 373 }, 374 }, 375 { 376 name: "with max-download-attempts", 377 config: &Config{ 378 CommonConfig: CommonConfig{ 379 MaxDownloadAttempts: intPtr(4), 380 }, 381 }, 382 }, 383 { 384 name: "with multiple node generic resources", 385 config: &Config{ 386 CommonConfig: CommonConfig{ 387 NodeGenericResources: []string{"foo=bar", "foo=baz"}, 388 }, 389 }, 390 }, 391 { 392 name: "with node generic resources", 393 config: &Config{ 394 CommonConfig: CommonConfig{ 395 NodeGenericResources: []string{"foo=1"}, 396 }, 397 }, 398 }, 399 } 400 for _, tc := range testCases { 401 t.Run(tc.name, func(t *testing.T) { 402 err := Validate(tc.config) 403 assert.NilError(t, err) 404 }) 405 } 406 } 407 408 func TestModifiedDiscoverySettings(t *testing.T) { 409 cases := []struct { 410 current *Config 411 modified *Config 412 expected bool 413 }{ 414 { 415 current: discoveryConfig("foo", "bar", map[string]string{}), 416 modified: discoveryConfig("foo", "bar", map[string]string{}), 417 expected: false, 418 }, 419 { 420 current: discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}), 421 modified: discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}), 422 expected: false, 423 }, 424 { 425 current: discoveryConfig("foo", "bar", map[string]string{}), 426 modified: discoveryConfig("foo", "bar", nil), 427 expected: false, 428 }, 429 { 430 current: discoveryConfig("foo", "bar", nil), 431 modified: discoveryConfig("foo", "bar", map[string]string{}), 432 expected: false, 433 }, 434 { 435 current: discoveryConfig("foo", "bar", nil), 436 modified: discoveryConfig("baz", "bar", nil), 437 expected: true, 438 }, 439 { 440 current: discoveryConfig("foo", "bar", nil), 441 modified: discoveryConfig("foo", "baz", nil), 442 expected: true, 443 }, 444 { 445 current: discoveryConfig("foo", "bar", nil), 446 modified: discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}), 447 expected: true, 448 }, 449 } 450 451 for _, c := range cases { 452 got := ModifiedDiscoverySettings(c.current, c.modified.ClusterStore, c.modified.ClusterAdvertise, c.modified.ClusterOpts) 453 if c.expected != got { 454 t.Fatalf("expected %v, got %v: current config %v, new config %v", c.expected, got, c.current, c.modified) 455 } 456 } 457 } 458 459 func discoveryConfig(backendAddr, advertiseAddr string, opts map[string]string) *Config { 460 return &Config{ 461 CommonConfig: CommonConfig{ 462 ClusterStore: backendAddr, 463 ClusterAdvertise: advertiseAddr, 464 ClusterOpts: opts, 465 }, 466 } 467 } 468 469 // TestReloadSetConfigFileNotExist tests that when `--config-file` is set 470 // and it doesn't exist the `Reload` function returns an error. 471 func TestReloadSetConfigFileNotExist(t *testing.T) { 472 configFile := "/tmp/blabla/not/exists/config.json" 473 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 474 flags.String("config-file", "", "") 475 flags.Set("config-file", configFile) 476 477 err := Reload(configFile, flags, func(c *Config) {}) 478 assert.Check(t, is.ErrorContains(err, "unable to configure the Docker daemon with file")) 479 } 480 481 // TestReloadDefaultConfigNotExist tests that if the default configuration file 482 // doesn't exist the daemon still will be reloaded. 483 func TestReloadDefaultConfigNotExist(t *testing.T) { 484 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 485 reloaded := false 486 configFile := "/etc/docker/daemon.json" 487 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 488 flags.String("config-file", configFile, "") 489 err := Reload(configFile, flags, func(c *Config) { 490 reloaded = true 491 }) 492 assert.Check(t, err) 493 assert.Check(t, reloaded) 494 } 495 496 // TestReloadBadDefaultConfig tests that when `--config-file` is not set 497 // and the default configuration file exists and is bad return an error 498 func TestReloadBadDefaultConfig(t *testing.T) { 499 f, err := ioutil.TempFile("", "docker-config-") 500 if err != nil { 501 t.Fatal(err) 502 } 503 504 configFile := f.Name() 505 f.Write([]byte(`{wrong: "configuration"}`)) 506 f.Close() 507 508 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 509 flags.String("config-file", configFile, "") 510 err = Reload(configFile, flags, func(c *Config) {}) 511 assert.Check(t, is.ErrorContains(err, "unable to configure the Docker daemon with file")) 512 } 513 514 func TestReloadWithConflictingLabels(t *testing.T) { 515 tempFile := fs.NewFile(t, "config", fs.WithContent(`{"labels":["foo=bar","foo=baz"]}`)) 516 defer tempFile.Remove() 517 configFile := tempFile.Path() 518 519 var lbls []string 520 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 521 flags.String("config-file", configFile, "") 522 flags.StringSlice("labels", lbls, "") 523 err := Reload(configFile, flags, func(c *Config) {}) 524 assert.Check(t, is.ErrorContains(err, "conflict labels for foo=baz and foo=bar")) 525 } 526 527 func TestReloadWithDuplicateLabels(t *testing.T) { 528 tempFile := fs.NewFile(t, "config", fs.WithContent(`{"labels":["foo=the-same","foo=the-same"]}`)) 529 defer tempFile.Remove() 530 configFile := tempFile.Path() 531 532 var lbls []string 533 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 534 flags.String("config-file", configFile, "") 535 flags.StringSlice("labels", lbls, "") 536 err := Reload(configFile, flags, func(c *Config) {}) 537 assert.Check(t, err) 538 }