github.com/ali-iotechsys/cli@v20.10.0+incompatible/cli/compose/loader/merge_test.go (about) 1 package loader 2 3 import ( 4 "reflect" 5 "testing" 6 7 "github.com/docker/cli/cli/compose/types" 8 "github.com/imdario/mergo" 9 "gotest.tools/v3/assert" 10 ) 11 12 func TestLoadTwoDifferentVersion(t *testing.T) { 13 configDetails := types.ConfigDetails{ 14 ConfigFiles: []types.ConfigFile{ 15 {Filename: "base.yml", Config: map[string]interface{}{ 16 "version": "3.1", 17 }}, 18 {Filename: "override.yml", Config: map[string]interface{}{ 19 "version": "3.4", 20 }}, 21 }, 22 } 23 _, err := Load(configDetails) 24 assert.Error(t, err, "version mismatched between two composefiles : 3.1 and 3.4") 25 } 26 27 func TestLoadLogging(t *testing.T) { 28 loggingCases := []struct { 29 name string 30 loggingBase map[string]interface{} 31 loggingOverride map[string]interface{} 32 expected *types.LoggingConfig 33 }{ 34 { 35 name: "no_override_driver", 36 loggingBase: map[string]interface{}{ 37 "logging": map[string]interface{}{ 38 "driver": "json-file", 39 "options": map[string]interface{}{ 40 "frequency": "2000", 41 "timeout": "23", 42 }, 43 }, 44 }, 45 loggingOverride: map[string]interface{}{ 46 "logging": map[string]interface{}{ 47 "options": map[string]interface{}{ 48 "timeout": "360", 49 "pretty-print": "on", 50 }, 51 }, 52 }, 53 expected: &types.LoggingConfig{ 54 Driver: "json-file", 55 Options: map[string]string{ 56 "frequency": "2000", 57 "timeout": "360", 58 "pretty-print": "on", 59 }, 60 }, 61 }, 62 { 63 name: "override_driver", 64 loggingBase: map[string]interface{}{ 65 "logging": map[string]interface{}{ 66 "driver": "json-file", 67 "options": map[string]interface{}{ 68 "frequency": "2000", 69 "timeout": "23", 70 }, 71 }, 72 }, 73 loggingOverride: map[string]interface{}{ 74 "logging": map[string]interface{}{ 75 "driver": "syslog", 76 "options": map[string]interface{}{ 77 "timeout": "360", 78 "pretty-print": "on", 79 }, 80 }, 81 }, 82 expected: &types.LoggingConfig{ 83 Driver: "syslog", 84 Options: map[string]string{ 85 "timeout": "360", 86 "pretty-print": "on", 87 }, 88 }, 89 }, 90 { 91 name: "no_base_driver", 92 loggingBase: map[string]interface{}{ 93 "logging": map[string]interface{}{ 94 "options": map[string]interface{}{ 95 "frequency": "2000", 96 "timeout": "23", 97 }, 98 }, 99 }, 100 loggingOverride: map[string]interface{}{ 101 "logging": map[string]interface{}{ 102 "driver": "json-file", 103 "options": map[string]interface{}{ 104 "timeout": "360", 105 "pretty-print": "on", 106 }, 107 }, 108 }, 109 expected: &types.LoggingConfig{ 110 Driver: "json-file", 111 Options: map[string]string{ 112 "frequency": "2000", 113 "timeout": "360", 114 "pretty-print": "on", 115 }, 116 }, 117 }, 118 { 119 name: "no_driver", 120 loggingBase: map[string]interface{}{ 121 "logging": map[string]interface{}{ 122 "options": map[string]interface{}{ 123 "frequency": "2000", 124 "timeout": "23", 125 }, 126 }, 127 }, 128 loggingOverride: map[string]interface{}{ 129 "logging": map[string]interface{}{ 130 "options": map[string]interface{}{ 131 "timeout": "360", 132 "pretty-print": "on", 133 }, 134 }, 135 }, 136 expected: &types.LoggingConfig{ 137 Options: map[string]string{ 138 "frequency": "2000", 139 "timeout": "360", 140 "pretty-print": "on", 141 }, 142 }, 143 }, 144 { 145 name: "no_override_options", 146 loggingBase: map[string]interface{}{ 147 "logging": map[string]interface{}{ 148 "driver": "json-file", 149 "options": map[string]interface{}{ 150 "frequency": "2000", 151 "timeout": "23", 152 }, 153 }, 154 }, 155 loggingOverride: map[string]interface{}{ 156 "logging": map[string]interface{}{ 157 "driver": "syslog", 158 }, 159 }, 160 expected: &types.LoggingConfig{ 161 Driver: "syslog", 162 }, 163 }, 164 { 165 name: "no_base", 166 loggingBase: map[string]interface{}{}, 167 loggingOverride: map[string]interface{}{ 168 "logging": map[string]interface{}{ 169 "driver": "json-file", 170 "options": map[string]interface{}{ 171 "frequency": "2000", 172 }, 173 }, 174 }, 175 expected: &types.LoggingConfig{ 176 Driver: "json-file", 177 Options: map[string]string{ 178 "frequency": "2000", 179 }, 180 }, 181 }, 182 } 183 184 for _, tc := range loggingCases { 185 t.Run(tc.name, func(t *testing.T) { 186 configDetails := types.ConfigDetails{ 187 ConfigFiles: []types.ConfigFile{ 188 { 189 Filename: "base.yml", 190 Config: map[string]interface{}{ 191 "version": "3.4", 192 "services": map[string]interface{}{ 193 "foo": tc.loggingBase, 194 }, 195 }, 196 }, 197 { 198 Filename: "override.yml", 199 Config: map[string]interface{}{ 200 "version": "3.4", 201 "services": map[string]interface{}{ 202 "foo": tc.loggingOverride, 203 }, 204 }, 205 }, 206 }, 207 } 208 config, err := Load(configDetails) 209 assert.NilError(t, err) 210 assert.DeepEqual(t, &types.Config{ 211 Filename: "base.yml", 212 Version: "3.4", 213 Services: []types.ServiceConfig{ 214 { 215 Name: "foo", 216 Logging: tc.expected, 217 Environment: types.MappingWithEquals{}, 218 }, 219 }, 220 Networks: map[string]types.NetworkConfig{}, 221 Volumes: map[string]types.VolumeConfig{}, 222 Secrets: map[string]types.SecretConfig{}, 223 Configs: map[string]types.ConfigObjConfig{}, 224 }, config) 225 }) 226 } 227 } 228 229 func TestLoadMultipleServicePorts(t *testing.T) { 230 portsCases := []struct { 231 name string 232 portBase map[string]interface{} 233 portOverride map[string]interface{} 234 expected []types.ServicePortConfig 235 }{ 236 { 237 name: "no_override", 238 portBase: map[string]interface{}{ 239 "ports": []interface{}{ 240 "8080:80", 241 }, 242 }, 243 portOverride: map[string]interface{}{}, 244 expected: []types.ServicePortConfig{ 245 { 246 Mode: "ingress", 247 Published: 8080, 248 Target: 80, 249 Protocol: "tcp", 250 }, 251 }, 252 }, 253 { 254 name: "override_different_published", 255 portBase: map[string]interface{}{ 256 "ports": []interface{}{ 257 "8080:80", 258 }, 259 }, 260 portOverride: map[string]interface{}{ 261 "ports": []interface{}{ 262 "8081:80", 263 }, 264 }, 265 expected: []types.ServicePortConfig{ 266 { 267 Mode: "ingress", 268 Published: 8080, 269 Target: 80, 270 Protocol: "tcp", 271 }, 272 { 273 Mode: "ingress", 274 Published: 8081, 275 Target: 80, 276 Protocol: "tcp", 277 }, 278 }, 279 }, 280 { 281 name: "override_same_published", 282 portBase: map[string]interface{}{ 283 "ports": []interface{}{ 284 "8080:80", 285 }, 286 }, 287 portOverride: map[string]interface{}{ 288 "ports": []interface{}{ 289 "8080:81", 290 }, 291 }, 292 expected: []types.ServicePortConfig{ 293 { 294 Mode: "ingress", 295 Published: 8080, 296 Target: 81, 297 Protocol: "tcp", 298 }, 299 }, 300 }, 301 } 302 303 for _, tc := range portsCases { 304 t.Run(tc.name, func(t *testing.T) { 305 configDetails := types.ConfigDetails{ 306 ConfigFiles: []types.ConfigFile{ 307 { 308 Filename: "base.yml", 309 Config: map[string]interface{}{ 310 "version": "3.4", 311 "services": map[string]interface{}{ 312 "foo": tc.portBase, 313 }, 314 }, 315 }, 316 { 317 Filename: "override.yml", 318 Config: map[string]interface{}{ 319 "version": "3.4", 320 "services": map[string]interface{}{ 321 "foo": tc.portOverride, 322 }, 323 }, 324 }, 325 }, 326 } 327 config, err := Load(configDetails) 328 assert.NilError(t, err) 329 assert.DeepEqual(t, &types.Config{ 330 Filename: "base.yml", 331 Version: "3.4", 332 Services: []types.ServiceConfig{ 333 { 334 Name: "foo", 335 Ports: tc.expected, 336 Environment: types.MappingWithEquals{}, 337 }, 338 }, 339 Networks: map[string]types.NetworkConfig{}, 340 Volumes: map[string]types.VolumeConfig{}, 341 Secrets: map[string]types.SecretConfig{}, 342 Configs: map[string]types.ConfigObjConfig{}, 343 }, config) 344 }) 345 } 346 } 347 348 func TestLoadMultipleSecretsConfig(t *testing.T) { 349 portsCases := []struct { 350 name string 351 secretBase map[string]interface{} 352 secretOverride map[string]interface{} 353 expected []types.ServiceSecretConfig 354 }{ 355 { 356 name: "no_override", 357 secretBase: map[string]interface{}{ 358 "secrets": []interface{}{ 359 "my_secret", 360 }, 361 }, 362 secretOverride: map[string]interface{}{}, 363 expected: []types.ServiceSecretConfig{ 364 { 365 Source: "my_secret", 366 }, 367 }, 368 }, 369 { 370 name: "override_simple", 371 secretBase: map[string]interface{}{ 372 "secrets": []interface{}{ 373 "foo_secret", 374 }, 375 }, 376 secretOverride: map[string]interface{}{ 377 "secrets": []interface{}{ 378 "bar_secret", 379 }, 380 }, 381 expected: []types.ServiceSecretConfig{ 382 { 383 Source: "bar_secret", 384 }, 385 { 386 Source: "foo_secret", 387 }, 388 }, 389 }, 390 { 391 name: "override_same_source", 392 secretBase: map[string]interface{}{ 393 "secrets": []interface{}{ 394 "foo_secret", 395 map[string]interface{}{ 396 "source": "bar_secret", 397 "target": "waw_secret", 398 }, 399 }, 400 }, 401 secretOverride: map[string]interface{}{ 402 "secrets": []interface{}{ 403 map[string]interface{}{ 404 "source": "bar_secret", 405 "target": "bof_secret", 406 }, 407 map[string]interface{}{ 408 "source": "baz_secret", 409 "target": "waw_secret", 410 }, 411 }, 412 }, 413 expected: []types.ServiceSecretConfig{ 414 { 415 Source: "bar_secret", 416 Target: "bof_secret", 417 }, 418 { 419 Source: "baz_secret", 420 Target: "waw_secret", 421 }, 422 { 423 Source: "foo_secret", 424 }, 425 }, 426 }, 427 } 428 429 for _, tc := range portsCases { 430 t.Run(tc.name, func(t *testing.T) { 431 configDetails := types.ConfigDetails{ 432 ConfigFiles: []types.ConfigFile{ 433 { 434 Filename: "base.yml", 435 Config: map[string]interface{}{ 436 "version": "3.4", 437 "services": map[string]interface{}{ 438 "foo": tc.secretBase, 439 }, 440 }, 441 }, 442 { 443 Filename: "override.yml", 444 Config: map[string]interface{}{ 445 "version": "3.4", 446 "services": map[string]interface{}{ 447 "foo": tc.secretOverride, 448 }, 449 }, 450 }, 451 }, 452 } 453 config, err := Load(configDetails) 454 assert.NilError(t, err) 455 assert.DeepEqual(t, &types.Config{ 456 Filename: "base.yml", 457 Version: "3.4", 458 Services: []types.ServiceConfig{ 459 { 460 Name: "foo", 461 Secrets: tc.expected, 462 Environment: types.MappingWithEquals{}, 463 }, 464 }, 465 Networks: map[string]types.NetworkConfig{}, 466 Volumes: map[string]types.VolumeConfig{}, 467 Secrets: map[string]types.SecretConfig{}, 468 Configs: map[string]types.ConfigObjConfig{}, 469 }, config) 470 }) 471 } 472 } 473 474 func TestLoadMultipleConfigobjsConfig(t *testing.T) { 475 portsCases := []struct { 476 name string 477 configBase map[string]interface{} 478 configOverride map[string]interface{} 479 expected []types.ServiceConfigObjConfig 480 }{ 481 { 482 name: "no_override", 483 configBase: map[string]interface{}{ 484 "configs": []interface{}{ 485 "my_config", 486 }, 487 }, 488 configOverride: map[string]interface{}{}, 489 expected: []types.ServiceConfigObjConfig{ 490 { 491 Source: "my_config", 492 }, 493 }, 494 }, 495 { 496 name: "override_simple", 497 configBase: map[string]interface{}{ 498 "configs": []interface{}{ 499 "foo_config", 500 }, 501 }, 502 configOverride: map[string]interface{}{ 503 "configs": []interface{}{ 504 "bar_config", 505 }, 506 }, 507 expected: []types.ServiceConfigObjConfig{ 508 { 509 Source: "bar_config", 510 }, 511 { 512 Source: "foo_config", 513 }, 514 }, 515 }, 516 { 517 name: "override_same_source", 518 configBase: map[string]interface{}{ 519 "configs": []interface{}{ 520 "foo_config", 521 map[string]interface{}{ 522 "source": "bar_config", 523 "target": "waw_config", 524 }, 525 }, 526 }, 527 configOverride: map[string]interface{}{ 528 "configs": []interface{}{ 529 map[string]interface{}{ 530 "source": "bar_config", 531 "target": "bof_config", 532 }, 533 map[string]interface{}{ 534 "source": "baz_config", 535 "target": "waw_config", 536 }, 537 }, 538 }, 539 expected: []types.ServiceConfigObjConfig{ 540 { 541 Source: "bar_config", 542 Target: "bof_config", 543 }, 544 { 545 Source: "baz_config", 546 Target: "waw_config", 547 }, 548 { 549 Source: "foo_config", 550 }, 551 }, 552 }, 553 } 554 555 for _, tc := range portsCases { 556 t.Run(tc.name, func(t *testing.T) { 557 configDetails := types.ConfigDetails{ 558 ConfigFiles: []types.ConfigFile{ 559 { 560 Filename: "base.yml", 561 Config: map[string]interface{}{ 562 "version": "3.4", 563 "services": map[string]interface{}{ 564 "foo": tc.configBase, 565 }, 566 }, 567 }, 568 { 569 Filename: "override.yml", 570 Config: map[string]interface{}{ 571 "version": "3.4", 572 "services": map[string]interface{}{ 573 "foo": tc.configOverride, 574 }, 575 }, 576 }, 577 }, 578 } 579 config, err := Load(configDetails) 580 assert.NilError(t, err) 581 assert.DeepEqual(t, &types.Config{ 582 Filename: "base.yml", 583 Version: "3.4", 584 Services: []types.ServiceConfig{ 585 { 586 Name: "foo", 587 Configs: tc.expected, 588 Environment: types.MappingWithEquals{}, 589 }, 590 }, 591 Networks: map[string]types.NetworkConfig{}, 592 Volumes: map[string]types.VolumeConfig{}, 593 Secrets: map[string]types.SecretConfig{}, 594 Configs: map[string]types.ConfigObjConfig{}, 595 }, config) 596 }) 597 } 598 } 599 600 func TestLoadMultipleUlimits(t *testing.T) { 601 ulimitCases := []struct { 602 name string 603 ulimitBase map[string]interface{} 604 ulimitOverride map[string]interface{} 605 expected map[string]*types.UlimitsConfig 606 }{ 607 { 608 name: "no_override", 609 ulimitBase: map[string]interface{}{ 610 "ulimits": map[string]interface{}{ 611 "noproc": 65535, 612 }, 613 }, 614 ulimitOverride: map[string]interface{}{}, 615 expected: map[string]*types.UlimitsConfig{ 616 "noproc": { 617 Single: 65535, 618 }, 619 }, 620 }, 621 { 622 name: "override_simple", 623 ulimitBase: map[string]interface{}{ 624 "ulimits": map[string]interface{}{ 625 "noproc": 65535, 626 }, 627 }, 628 ulimitOverride: map[string]interface{}{ 629 "ulimits": map[string]interface{}{ 630 "noproc": 44444, 631 }, 632 }, 633 expected: map[string]*types.UlimitsConfig{ 634 "noproc": { 635 Single: 44444, 636 }, 637 }, 638 }, 639 { 640 name: "override_different_notation", 641 ulimitBase: map[string]interface{}{ 642 "ulimits": map[string]interface{}{ 643 "nofile": map[string]interface{}{ 644 "soft": 11111, 645 "hard": 99999, 646 }, 647 "noproc": 44444, 648 }, 649 }, 650 ulimitOverride: map[string]interface{}{ 651 "ulimits": map[string]interface{}{ 652 "nofile": 55555, 653 "noproc": map[string]interface{}{ 654 "soft": 22222, 655 "hard": 33333, 656 }, 657 }, 658 }, 659 expected: map[string]*types.UlimitsConfig{ 660 "noproc": { 661 Soft: 22222, 662 Hard: 33333, 663 }, 664 "nofile": { 665 Single: 55555, 666 }, 667 }, 668 }, 669 } 670 671 for _, tc := range ulimitCases { 672 t.Run(tc.name, func(t *testing.T) { 673 configDetails := types.ConfigDetails{ 674 ConfigFiles: []types.ConfigFile{ 675 { 676 Filename: "base.yml", 677 Config: map[string]interface{}{ 678 "version": "3.4", 679 "services": map[string]interface{}{ 680 "foo": tc.ulimitBase, 681 }, 682 }, 683 }, 684 { 685 Filename: "override.yml", 686 Config: map[string]interface{}{ 687 "version": "3.4", 688 "services": map[string]interface{}{ 689 "foo": tc.ulimitOverride, 690 }, 691 }, 692 }, 693 }, 694 } 695 config, err := Load(configDetails) 696 assert.NilError(t, err) 697 assert.DeepEqual(t, &types.Config{ 698 Filename: "base.yml", 699 Version: "3.4", 700 Services: []types.ServiceConfig{ 701 { 702 Name: "foo", 703 Ulimits: tc.expected, 704 Environment: types.MappingWithEquals{}, 705 }, 706 }, 707 Networks: map[string]types.NetworkConfig{}, 708 Volumes: map[string]types.VolumeConfig{}, 709 Secrets: map[string]types.SecretConfig{}, 710 Configs: map[string]types.ConfigObjConfig{}, 711 }, config) 712 }) 713 } 714 } 715 716 func TestLoadMultipleServiceNetworks(t *testing.T) { 717 networkCases := []struct { 718 name string 719 networkBase map[string]interface{} 720 networkOverride map[string]interface{} 721 expected map[string]*types.ServiceNetworkConfig 722 }{ 723 { 724 name: "no_override", 725 networkBase: map[string]interface{}{ 726 "networks": []interface{}{ 727 "net1", 728 "net2", 729 }, 730 }, 731 networkOverride: map[string]interface{}{}, 732 expected: map[string]*types.ServiceNetworkConfig{ 733 "net1": nil, 734 "net2": nil, 735 }, 736 }, 737 { 738 name: "override_simple", 739 networkBase: map[string]interface{}{ 740 "networks": []interface{}{ 741 "net1", 742 "net2", 743 }, 744 }, 745 networkOverride: map[string]interface{}{ 746 "networks": []interface{}{ 747 "net1", 748 "net3", 749 }, 750 }, 751 expected: map[string]*types.ServiceNetworkConfig{ 752 "net1": nil, 753 "net2": nil, 754 "net3": nil, 755 }, 756 }, 757 { 758 name: "override_with_aliases", 759 networkBase: map[string]interface{}{ 760 "networks": map[string]interface{}{ 761 "net1": map[string]interface{}{ 762 "aliases": []interface{}{ 763 "alias1", 764 }, 765 }, 766 "net2": nil, 767 }, 768 }, 769 networkOverride: map[string]interface{}{ 770 "networks": map[string]interface{}{ 771 "net1": map[string]interface{}{ 772 "aliases": []interface{}{ 773 "alias2", 774 "alias3", 775 }, 776 }, 777 "net3": map[string]interface{}{}, 778 }, 779 }, 780 expected: map[string]*types.ServiceNetworkConfig{ 781 "net1": { 782 Aliases: []string{"alias2", "alias3"}, 783 }, 784 "net2": nil, 785 "net3": {}, 786 }, 787 }, 788 } 789 790 for _, tc := range networkCases { 791 t.Run(tc.name, func(t *testing.T) { 792 configDetails := types.ConfigDetails{ 793 ConfigFiles: []types.ConfigFile{ 794 { 795 Filename: "base.yml", 796 Config: map[string]interface{}{ 797 "version": "3.4", 798 "services": map[string]interface{}{ 799 "foo": tc.networkBase, 800 }, 801 }, 802 }, 803 { 804 Filename: "override.yml", 805 Config: map[string]interface{}{ 806 "version": "3.4", 807 "services": map[string]interface{}{ 808 "foo": tc.networkOverride, 809 }, 810 }, 811 }, 812 }, 813 } 814 config, err := Load(configDetails) 815 assert.NilError(t, err) 816 assert.DeepEqual(t, &types.Config{ 817 Filename: "base.yml", 818 Version: "3.4", 819 Services: []types.ServiceConfig{ 820 { 821 Name: "foo", 822 Networks: tc.expected, 823 Environment: types.MappingWithEquals{}, 824 }, 825 }, 826 Networks: map[string]types.NetworkConfig{}, 827 Volumes: map[string]types.VolumeConfig{}, 828 Secrets: map[string]types.SecretConfig{}, 829 Configs: map[string]types.ConfigObjConfig{}, 830 }, config) 831 }) 832 } 833 } 834 835 func TestLoadMultipleConfigs(t *testing.T) { 836 base := map[string]interface{}{ 837 "version": "3.4", 838 "services": map[string]interface{}{ 839 "foo": map[string]interface{}{ 840 "image": "foo", 841 "build": map[string]interface{}{ 842 "context": ".", 843 "dockerfile": "bar.Dockerfile", 844 }, 845 "ports": []interface{}{ 846 "8080:80", 847 "9090:90", 848 }, 849 "labels": []interface{}{ 850 "foo=bar", 851 }, 852 "cap_add": []interface{}{ 853 "NET_ADMIN", 854 }, 855 }, 856 }, 857 "volumes": map[string]interface{}{}, 858 "networks": map[string]interface{}{}, 859 "secrets": map[string]interface{}{}, 860 "configs": map[string]interface{}{}, 861 } 862 override := map[string]interface{}{ 863 "version": "3.4", 864 "services": map[string]interface{}{ 865 "foo": map[string]interface{}{ 866 "image": "baz", 867 "build": map[string]interface{}{ 868 "dockerfile": "foo.Dockerfile", 869 "args": []interface{}{ 870 "buildno=1", 871 "password=secret", 872 }, 873 }, 874 "ports": []interface{}{ 875 map[string]interface{}{ 876 "target": 81, 877 "published": 8080, 878 }, 879 }, 880 "labels": map[string]interface{}{ 881 "foo": "baz", 882 }, 883 "cap_add": []interface{}{ 884 "SYS_ADMIN", 885 }, 886 }, 887 "bar": map[string]interface{}{ 888 "image": "bar", 889 }, 890 }, 891 "volumes": map[string]interface{}{}, 892 "networks": map[string]interface{}{}, 893 "secrets": map[string]interface{}{}, 894 "configs": map[string]interface{}{}, 895 } 896 configDetails := types.ConfigDetails{ 897 ConfigFiles: []types.ConfigFile{ 898 {Filename: "base.yml", Config: base}, 899 {Filename: "override.yml", Config: override}, 900 }, 901 } 902 config, err := Load(configDetails) 903 assert.NilError(t, err) 904 assert.DeepEqual(t, &types.Config{ 905 Filename: "base.yml", 906 Version: "3.4", 907 Services: []types.ServiceConfig{ 908 { 909 Name: "bar", 910 Image: "bar", 911 Environment: types.MappingWithEquals{}, 912 }, 913 { 914 Name: "foo", 915 Image: "baz", 916 Build: types.BuildConfig{ 917 Context: ".", 918 Dockerfile: "foo.Dockerfile", 919 Args: types.MappingWithEquals{ 920 "buildno": strPtr("1"), 921 "password": strPtr("secret"), 922 }, 923 }, 924 Ports: []types.ServicePortConfig{ 925 { 926 Target: 81, 927 Published: 8080, 928 }, 929 { 930 Mode: "ingress", 931 Target: 90, 932 Published: 9090, 933 Protocol: "tcp", 934 }, 935 }, 936 Labels: types.Labels{ 937 "foo": "baz", 938 }, 939 CapAdd: []string{"NET_ADMIN", "SYS_ADMIN"}, 940 Environment: types.MappingWithEquals{}, 941 }}, 942 Networks: map[string]types.NetworkConfig{}, 943 Volumes: map[string]types.VolumeConfig{}, 944 Secrets: map[string]types.SecretConfig{}, 945 Configs: map[string]types.ConfigObjConfig{}, 946 }, config) 947 } 948 949 // Issue#972 950 func TestLoadMultipleNetworks(t *testing.T) { 951 base := map[string]interface{}{ 952 "version": "3.4", 953 "services": map[string]interface{}{ 954 "foo": map[string]interface{}{ 955 "image": "baz", 956 }, 957 }, 958 "volumes": map[string]interface{}{}, 959 "networks": map[string]interface{}{ 960 "hostnet": map[string]interface{}{ 961 "driver": "overlay", 962 "ipam": map[string]interface{}{ 963 "driver": "default", 964 "config": []interface{}{ 965 map[string]interface{}{ 966 "subnet": "10.0.0.0/20", 967 }, 968 }, 969 }, 970 }, 971 }, 972 "secrets": map[string]interface{}{}, 973 "configs": map[string]interface{}{}, 974 } 975 override := map[string]interface{}{ 976 "version": "3.4", 977 "services": map[string]interface{}{}, 978 "volumes": map[string]interface{}{}, 979 "networks": map[string]interface{}{ 980 "hostnet": map[string]interface{}{ 981 "external": map[string]interface{}{ 982 "name": "host", 983 }, 984 }, 985 }, 986 "secrets": map[string]interface{}{}, 987 "configs": map[string]interface{}{}, 988 } 989 configDetails := types.ConfigDetails{ 990 ConfigFiles: []types.ConfigFile{ 991 {Filename: "base.yml", Config: base}, 992 {Filename: "override.yml", Config: override}, 993 }, 994 } 995 config, err := Load(configDetails) 996 assert.NilError(t, err) 997 assert.DeepEqual(t, &types.Config{ 998 Filename: "base.yml", 999 Version: "3.4", 1000 Services: []types.ServiceConfig{ 1001 { 1002 Name: "foo", 1003 Image: "baz", 1004 Environment: types.MappingWithEquals{}, 1005 }}, 1006 Networks: map[string]types.NetworkConfig{ 1007 "hostnet": { 1008 Name: "host", 1009 External: types.External{ 1010 External: true, 1011 }, 1012 }, 1013 }, 1014 Volumes: map[string]types.VolumeConfig{}, 1015 Secrets: map[string]types.SecretConfig{}, 1016 Configs: map[string]types.ConfigObjConfig{}, 1017 }, config) 1018 } 1019 1020 func TestMergeUlimitsConfig(t *testing.T) { 1021 specials := &specials{ 1022 m: map[reflect.Type]func(dst, src reflect.Value) error{ 1023 reflect.TypeOf(&types.UlimitsConfig{}): mergeUlimitsConfig, 1024 }, 1025 } 1026 base := map[string]*types.UlimitsConfig{ 1027 "override-single": {Single: 100}, 1028 "override-single-with-soft-hard": {Single: 200}, 1029 "override-soft-hard": {Soft: 300, Hard: 301}, 1030 "override-soft-hard-with-single": {Soft: 400, Hard: 401}, 1031 "dont-override": {Single: 500}, 1032 } 1033 override := map[string]*types.UlimitsConfig{ 1034 "override-single": {Single: 110}, 1035 "override-single-with-soft-hard": {Soft: 210, Hard: 211}, 1036 "override-soft-hard": {Soft: 310, Hard: 311}, 1037 "override-soft-hard-with-single": {Single: 410}, 1038 "add": {Single: 610}, 1039 } 1040 err := mergo.Merge(&base, &override, mergo.WithOverride, mergo.WithTransformers(specials)) 1041 assert.NilError(t, err) 1042 assert.DeepEqual( 1043 t, 1044 base, 1045 map[string]*types.UlimitsConfig{ 1046 "override-single": {Single: 110}, 1047 "override-single-with-soft-hard": {Soft: 210, Hard: 211}, 1048 "override-soft-hard": {Soft: 310, Hard: 311}, 1049 "override-soft-hard-with-single": {Single: 410}, 1050 "dont-override": {Single: 500}, 1051 "add": {Single: 610}, 1052 }, 1053 ) 1054 } 1055 1056 func TestMergeServiceNetworkConfig(t *testing.T) { 1057 specials := &specials{ 1058 m: map[reflect.Type]func(dst, src reflect.Value) error{ 1059 reflect.TypeOf(&types.ServiceNetworkConfig{}): mergeServiceNetworkConfig, 1060 }, 1061 } 1062 base := map[string]*types.ServiceNetworkConfig{ 1063 "override-aliases": { 1064 Aliases: []string{"100", "101"}, 1065 Ipv4Address: "127.0.0.1", 1066 Ipv6Address: "0:0:0:0:0:0:0:1", 1067 }, 1068 "dont-override": { 1069 Aliases: []string{"200", "201"}, 1070 Ipv4Address: "127.0.0.2", 1071 Ipv6Address: "0:0:0:0:0:0:0:2", 1072 }, 1073 } 1074 override := map[string]*types.ServiceNetworkConfig{ 1075 "override-aliases": { 1076 Aliases: []string{"110", "111"}, 1077 Ipv4Address: "127.0.1.1", 1078 Ipv6Address: "0:0:0:0:0:0:1:1", 1079 }, 1080 "add": { 1081 Aliases: []string{"310", "311"}, 1082 Ipv4Address: "127.0.3.1", 1083 Ipv6Address: "0:0:0:0:0:0:3:1", 1084 }, 1085 } 1086 err := mergo.Merge(&base, &override, mergo.WithOverride, mergo.WithTransformers(specials)) 1087 assert.NilError(t, err) 1088 assert.DeepEqual( 1089 t, 1090 base, 1091 map[string]*types.ServiceNetworkConfig{ 1092 "override-aliases": { 1093 Aliases: []string{"110", "111"}, 1094 Ipv4Address: "127.0.1.1", 1095 Ipv6Address: "0:0:0:0:0:0:1:1", 1096 }, 1097 "dont-override": { 1098 Aliases: []string{"200", "201"}, 1099 Ipv4Address: "127.0.0.2", 1100 Ipv6Address: "0:0:0:0:0:0:0:2", 1101 }, 1102 "add": { 1103 Aliases: []string{"310", "311"}, 1104 Ipv4Address: "127.0.3.1", 1105 Ipv6Address: "0:0:0:0:0:0:3:1", 1106 }, 1107 }, 1108 ) 1109 }