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  }