You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
363 lines
11 KiB
Go
363 lines
11 KiB
Go
/*
|
|
Copyright 2020 The Compose Specification Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package loader
|
|
|
|
import (
|
|
"reflect"
|
|
"sort"
|
|
|
|
"github.com/compose-spec/compose-go/types"
|
|
"github.com/imdario/mergo"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type specials struct {
|
|
m map[reflect.Type]func(dst, src reflect.Value) error
|
|
}
|
|
|
|
var serviceSpecials = &specials{
|
|
m: map[reflect.Type]func(dst, src reflect.Value) error{
|
|
reflect.TypeOf(&types.LoggingConfig{}): safelyMerge(mergeLoggingConfig),
|
|
reflect.TypeOf(&types.UlimitsConfig{}): safelyMerge(mergeUlimitsConfig),
|
|
reflect.TypeOf([]types.ServiceVolumeConfig{}): mergeSlice(toServiceVolumeConfigsMap, toServiceVolumeConfigsSlice),
|
|
reflect.TypeOf([]types.ServicePortConfig{}): mergeSlice(toServicePortConfigsMap, toServicePortConfigsSlice),
|
|
reflect.TypeOf([]types.ServiceSecretConfig{}): mergeSlice(toServiceSecretConfigsMap, toServiceSecretConfigsSlice),
|
|
reflect.TypeOf([]types.ServiceConfigObjConfig{}): mergeSlice(toServiceConfigObjConfigsMap, toSServiceConfigObjConfigsSlice),
|
|
reflect.TypeOf(&types.UlimitsConfig{}): mergeUlimitsConfig,
|
|
reflect.TypeOf(&types.ServiceNetworkConfig{}): mergeServiceNetworkConfig,
|
|
},
|
|
}
|
|
|
|
func (s *specials) Transformer(t reflect.Type) func(dst, src reflect.Value) error {
|
|
if fn, ok := s.m[t]; ok {
|
|
return fn
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func merge(configs []*types.Config) (*types.Config, error) {
|
|
base := configs[0]
|
|
for _, override := range configs[1:] {
|
|
var err error
|
|
base.Name = mergeNames(base.Name, override.Name)
|
|
base.Services, err = mergeServices(base.Services, override.Services)
|
|
if err != nil {
|
|
return base, errors.Wrapf(err, "cannot merge services from %s", override.Filename)
|
|
}
|
|
base.Volumes, err = mergeVolumes(base.Volumes, override.Volumes)
|
|
if err != nil {
|
|
return base, errors.Wrapf(err, "cannot merge volumes from %s", override.Filename)
|
|
}
|
|
base.Networks, err = mergeNetworks(base.Networks, override.Networks)
|
|
if err != nil {
|
|
return base, errors.Wrapf(err, "cannot merge networks from %s", override.Filename)
|
|
}
|
|
base.Secrets, err = mergeSecrets(base.Secrets, override.Secrets)
|
|
if err != nil {
|
|
return base, errors.Wrapf(err, "cannot merge secrets from %s", override.Filename)
|
|
}
|
|
base.Configs, err = mergeConfigs(base.Configs, override.Configs)
|
|
if err != nil {
|
|
return base, errors.Wrapf(err, "cannot merge configs from %s", override.Filename)
|
|
}
|
|
base.Extensions, err = mergeExtensions(base.Extensions, override.Extensions)
|
|
if err != nil {
|
|
return base, errors.Wrapf(err, "cannot merge extensions from %s", override.Filename)
|
|
}
|
|
}
|
|
return base, nil
|
|
}
|
|
|
|
func mergeNames(base, override string) string {
|
|
if override != "" {
|
|
return override
|
|
}
|
|
return base
|
|
}
|
|
|
|
func mergeServices(base, override []types.ServiceConfig) ([]types.ServiceConfig, error) {
|
|
baseServices := mapByName(base)
|
|
overrideServices := mapByName(override)
|
|
for name, overrideService := range overrideServices {
|
|
overrideService := overrideService
|
|
if baseService, ok := baseServices[name]; ok {
|
|
merged, err := _merge(&baseService, &overrideService)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "cannot merge service %s", name)
|
|
}
|
|
baseServices[name] = *merged
|
|
continue
|
|
}
|
|
baseServices[name] = overrideService
|
|
}
|
|
services := []types.ServiceConfig{}
|
|
for _, baseService := range baseServices {
|
|
services = append(services, baseService)
|
|
}
|
|
sort.Slice(services, func(i, j int) bool { return services[i].Name < services[j].Name })
|
|
return services, nil
|
|
}
|
|
|
|
func _merge(baseService *types.ServiceConfig, overrideService *types.ServiceConfig) (*types.ServiceConfig, error) {
|
|
if err := mergo.Merge(baseService, overrideService, mergo.WithAppendSlice, mergo.WithOverride, mergo.WithTransformers(serviceSpecials)); err != nil {
|
|
return nil, err
|
|
}
|
|
if overrideService.Command != nil {
|
|
baseService.Command = overrideService.Command
|
|
}
|
|
if overrideService.Entrypoint != nil {
|
|
baseService.Entrypoint = overrideService.Entrypoint
|
|
}
|
|
if baseService.Environment != nil {
|
|
baseService.Environment.OverrideBy(overrideService.Environment)
|
|
} else {
|
|
baseService.Environment = overrideService.Environment
|
|
}
|
|
return baseService, nil
|
|
}
|
|
|
|
func toServiceSecretConfigsMap(s interface{}) (map[interface{}]interface{}, error) {
|
|
secrets, ok := s.([]types.ServiceSecretConfig)
|
|
if !ok {
|
|
return nil, errors.Errorf("not a serviceSecretConfig: %v", s)
|
|
}
|
|
m := map[interface{}]interface{}{}
|
|
for _, secret := range secrets {
|
|
m[secret.Source] = secret
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
func toServiceConfigObjConfigsMap(s interface{}) (map[interface{}]interface{}, error) {
|
|
secrets, ok := s.([]types.ServiceConfigObjConfig)
|
|
if !ok {
|
|
return nil, errors.Errorf("not a serviceSecretConfig: %v", s)
|
|
}
|
|
m := map[interface{}]interface{}{}
|
|
for _, secret := range secrets {
|
|
m[secret.Source] = secret
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
func toServicePortConfigsMap(s interface{}) (map[interface{}]interface{}, error) {
|
|
ports, ok := s.([]types.ServicePortConfig)
|
|
if !ok {
|
|
return nil, errors.Errorf("not a servicePortConfig slice: %v", s)
|
|
}
|
|
m := map[interface{}]interface{}{}
|
|
type port struct {
|
|
target uint32
|
|
published string
|
|
ip string
|
|
protocol string
|
|
}
|
|
|
|
for _, p := range ports {
|
|
mergeKey := port{
|
|
target: p.Target,
|
|
published: p.Published,
|
|
ip: p.HostIP,
|
|
protocol: p.Protocol,
|
|
}
|
|
m[mergeKey] = p
|
|
}
|
|
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 {
|
|
var s []types.ServiceSecretConfig
|
|
for _, v := range m {
|
|
s = append(s, v.(types.ServiceSecretConfig))
|
|
}
|
|
sort.Slice(s, func(i, j int) bool { return s[i].Source < s[j].Source })
|
|
dst.Set(reflect.ValueOf(s))
|
|
return nil
|
|
}
|
|
|
|
func toSServiceConfigObjConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error {
|
|
var s []types.ServiceConfigObjConfig
|
|
for _, v := range m {
|
|
s = append(s, v.(types.ServiceConfigObjConfig))
|
|
}
|
|
sort.Slice(s, func(i, j int) bool { return s[i].Source < s[j].Source })
|
|
dst.Set(reflect.ValueOf(s))
|
|
return nil
|
|
}
|
|
|
|
func toServicePortConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error {
|
|
var s []types.ServicePortConfig
|
|
for _, v := range m {
|
|
s = append(s, v.(types.ServicePortConfig))
|
|
}
|
|
sort.Slice(s, func(i, j int) bool {
|
|
if s[i].Target != s[j].Target {
|
|
return s[i].Target < s[j].Target
|
|
}
|
|
if s[i].Published != s[j].Published {
|
|
return s[i].Published < s[j].Published
|
|
}
|
|
if s[i].HostIP != s[j].HostIP {
|
|
return s[i].HostIP < s[j].HostIP
|
|
}
|
|
return s[i].Protocol < s[j].Protocol
|
|
})
|
|
dst.Set(reflect.ValueOf(s))
|
|
return nil
|
|
}
|
|
|
|
func toServiceVolumeConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error {
|
|
var 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
|
|
|
|
func safelyMerge(mergeFn func(dst, src reflect.Value) error) func(dst, src reflect.Value) error {
|
|
return func(dst, src reflect.Value) error {
|
|
if src.IsNil() {
|
|
return nil
|
|
}
|
|
if dst.IsNil() {
|
|
dst.Set(src)
|
|
return nil
|
|
}
|
|
return mergeFn(dst, src)
|
|
}
|
|
}
|
|
|
|
func mergeSlice(toMap toMapFn, writeValue writeValueFromMapFn) func(dst, src reflect.Value) error {
|
|
return func(dst, src reflect.Value) error {
|
|
dstMap, err := sliceToMap(toMap, dst)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
srcMap, err := sliceToMap(toMap, src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := mergo.Map(&dstMap, srcMap, mergo.WithOverride); err != nil {
|
|
return err
|
|
}
|
|
return writeValue(dst, dstMap)
|
|
}
|
|
}
|
|
|
|
func sliceToMap(toMap toMapFn, v reflect.Value) (map[interface{}]interface{}, error) {
|
|
// check if valid
|
|
if !v.IsValid() {
|
|
return nil, errors.Errorf("invalid value : %+v", v)
|
|
}
|
|
return toMap(v.Interface())
|
|
}
|
|
|
|
func mergeLoggingConfig(dst, src reflect.Value) error {
|
|
// Same driver, merging options
|
|
if getLoggingDriver(dst.Elem()) == getLoggingDriver(src.Elem()) ||
|
|
getLoggingDriver(dst.Elem()) == "" || getLoggingDriver(src.Elem()) == "" {
|
|
if getLoggingDriver(dst.Elem()) == "" {
|
|
dst.Elem().FieldByName("Driver").SetString(getLoggingDriver(src.Elem()))
|
|
}
|
|
dstOptions := dst.Elem().FieldByName("Options").Interface().(map[string]string)
|
|
srcOptions := src.Elem().FieldByName("Options").Interface().(map[string]string)
|
|
return mergo.Merge(&dstOptions, srcOptions, mergo.WithOverride)
|
|
}
|
|
// Different driver, override with src
|
|
dst.Set(src)
|
|
return nil
|
|
}
|
|
|
|
//nolint: unparam
|
|
func mergeUlimitsConfig(dst, src reflect.Value) error {
|
|
if src.Interface() != reflect.Zero(reflect.TypeOf(src.Interface())).Interface() {
|
|
dst.Elem().Set(src.Elem())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//nolint: unparam
|
|
func mergeServiceNetworkConfig(dst, src reflect.Value) error {
|
|
if src.Interface() != reflect.Zero(reflect.TypeOf(src.Interface())).Interface() {
|
|
dst.Elem().FieldByName("Aliases").Set(src.Elem().FieldByName("Aliases"))
|
|
if ipv4 := src.Elem().FieldByName("Ipv4Address").Interface().(string); ipv4 != "" {
|
|
dst.Elem().FieldByName("Ipv4Address").SetString(ipv4)
|
|
}
|
|
if ipv6 := src.Elem().FieldByName("Ipv6Address").Interface().(string); ipv6 != "" {
|
|
dst.Elem().FieldByName("Ipv6Address").SetString(ipv6)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getLoggingDriver(v reflect.Value) string {
|
|
return v.FieldByName("Driver").String()
|
|
}
|
|
|
|
func mapByName(services []types.ServiceConfig) map[string]types.ServiceConfig {
|
|
m := map[string]types.ServiceConfig{}
|
|
for _, service := range services {
|
|
m[service.Name] = service
|
|
}
|
|
return m
|
|
}
|
|
|
|
func mergeVolumes(base, override map[string]types.VolumeConfig) (map[string]types.VolumeConfig, error) {
|
|
err := mergo.Map(&base, &override, mergo.WithOverride)
|
|
return base, err
|
|
}
|
|
|
|
func mergeNetworks(base, override map[string]types.NetworkConfig) (map[string]types.NetworkConfig, error) {
|
|
err := mergo.Map(&base, &override, mergo.WithOverride)
|
|
return base, err
|
|
}
|
|
|
|
func mergeSecrets(base, override map[string]types.SecretConfig) (map[string]types.SecretConfig, error) {
|
|
err := mergo.Map(&base, &override, mergo.WithOverride)
|
|
return base, err
|
|
}
|
|
|
|
func mergeConfigs(base, override map[string]types.ConfigObjConfig) (map[string]types.ConfigObjConfig, error) {
|
|
err := mergo.Map(&base, &override, mergo.WithOverride)
|
|
return base, err
|
|
}
|
|
|
|
func mergeExtensions(base, override map[string]interface{}) (map[string]interface{}, error) {
|
|
if base == nil {
|
|
base = map[string]interface{}{}
|
|
}
|
|
err := mergo.Map(&base, &override, mergo.WithOverride)
|
|
return base, err
|
|
}
|