diff --git a/cli/compose/loader/merge.go b/cli/compose/loader/merge.go index 0de8d8a1b6..a2bdabec73 100644 --- a/cli/compose/loader/merge.go +++ b/cli/compose/loader/merge.go @@ -58,6 +58,7 @@ func mergeServices(base, override []types.ServiceConfig) ([]types.ServiceConfig, reflect.TypeOf([]types.ServiceSecretConfig{}): mergeSlice(toServiceSecretConfigsMap, toServiceSecretConfigsSlice), reflect.TypeOf([]types.ServiceConfigObjConfig{}): mergeSlice(toServiceConfigObjConfigsMap, toSServiceConfigObjConfigsSlice), reflect.TypeOf(&types.UlimitsConfig{}): mergeUlimitsConfig, + reflect.TypeOf([]types.ServiceVolumeConfig{}): mergeSlice(toServiceVolumeConfigsMap, toServiceVolumeConfigsSlice), reflect.TypeOf(&types.ServiceNetworkConfig{}): mergeServiceNetworkConfig, }, } @@ -116,6 +117,18 @@ func toServicePortConfigsMap(s interface{}) (map[interface{}]interface{}, error) return m, nil } +func toServiceVolumeConfigsMap(s interface{}) (map[interface{}]interface{}, error) { + volumes, ok := s.([]types.ServiceVolumeConfig) + if !ok { + return nil, errors.Errorf("not a serviceVolumeConfig slice: %v", s) + } + m := map[interface{}]interface{}{} + for _, v := range volumes { + m[v.Target] = v + } + return m, nil +} + func toServiceSecretConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error { s := []types.ServiceSecretConfig{} for _, v := range m { @@ -146,6 +159,16 @@ func toServicePortConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) return nil } +func toServiceVolumeConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error { + s := []types.ServiceVolumeConfig{} + for _, v := range m { + s = append(s, v.(types.ServiceVolumeConfig)) + } + sort.Slice(s, func(i, j int) bool { return s[i].Target < s[j].Target }) + dst.Set(reflect.ValueOf(s)) + return nil +} + type tomapFn func(s interface{}) (map[interface{}]interface{}, error) type writeValueFromMapFn func(reflect.Value, map[interface{}]interface{}) error @@ -211,6 +234,14 @@ func mergeUlimitsConfig(dst, src reflect.Value) error { return nil } +//nolint: unparam +func mergeServiceVolumeConfig(dst, src reflect.Value) error { + if dst.Elem().FieldByName("target").String() == src.Elem().FieldByName("target").String() { + dst.Set(src.Elem()) + } + return nil +} + //nolint: unparam func mergeServiceNetworkConfig(dst, src reflect.Value) error { if src.Interface() != reflect.Zero(reflect.TypeOf(src.Interface())).Interface() { diff --git a/cli/compose/loader/merge_test.go b/cli/compose/loader/merge_test.go index 4828df8548..c5bbf3b5de 100644 --- a/cli/compose/loader/merge_test.go +++ b/cli/compose/loader/merge_test.go @@ -1017,6 +1017,81 @@ func TestLoadMultipleNetworks(t *testing.T) { }, config) } +func TestLoadMultipleServiceVolumes(t *testing.T) { + base := map[string]interface{}{ + "version": "3.7", + "services": map[string]interface{}{ + "foo": map[string]interface{}{ + "image": "baz", + "volumes": []interface{}{ + map[string]interface{}{ + "type": "volume", + "source": "sourceVolume", + "target": "/var/app", + }, + }, + }, + }, + "volumes": map[string]interface{}{ + "sourceVolume": map[string]interface{}{}, + }, + "networks": map[string]interface{}{}, + "secrets": map[string]interface{}{}, + "configs": map[string]interface{}{}, + } + override := map[string]interface{}{ + "version": "3.7", + "services": map[string]interface{}{ + "foo": map[string]interface{}{ + "image": "baz", + "volumes": []interface{}{ + map[string]interface{}{ + "type": "volume", + "source": "/local", + "target": "/var/app", + }, + }, + }, + }, + "volumes": map[string]interface{}{}, + "networks": map[string]interface{}{}, + "secrets": map[string]interface{}{}, + "configs": map[string]interface{}{}, + } + configDetails := types.ConfigDetails{ + ConfigFiles: []types.ConfigFile{ + {Filename: "base.yml", Config: base}, + {Filename: "override.yml", Config: override}, + }, + } + config, err := Load(configDetails) + assert.NilError(t, err) + assert.DeepEqual(t, &types.Config{ + Filename: "base.yml", + Version: "3.7", + Services: []types.ServiceConfig{ + { + Name: "foo", + Image: "baz", + Environment: types.MappingWithEquals{}, + Volumes: []types.ServiceVolumeConfig{ + { + Type: "volume", + Source: "/local", + Target: "/var/app", + }, + }, + }, + }, + Volumes: map[string]types.VolumeConfig{ + "sourceVolume": {}, + }, + Secrets: map[string]types.SecretConfig{}, + Configs: map[string]types.ConfigObjConfig{}, + Networks: map[string]types.NetworkConfig{}, + }, config) +} + func TestMergeUlimitsConfig(t *testing.T) { specials := &specials{ m: map[reflect.Type]func(dst, src reflect.Value) error{