mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 01:53:42 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			379 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			379 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,
 | 
						|
	},
 | 
						|
}
 | 
						|
 | 
						|
func (s *specials) Transformer(t reflect.Type) func(dst, src reflect.Value) error {
 | 
						|
	// TODO this is a workaround waiting for imdario/mergo#131
 | 
						|
	if t.Kind() == reflect.Pointer && t.Elem().Kind() == reflect.Bool {
 | 
						|
		return func(dst, src reflect.Value) error {
 | 
						|
			if dst.CanSet() && !src.IsNil() {
 | 
						|
				dst.Set(src)
 | 
						|
			}
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	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.HealthCheck != nil && overrideService.HealthCheck.Test != nil {
 | 
						|
		baseService.HealthCheck.Test = overrideService.HealthCheck.Test
 | 
						|
	}
 | 
						|
	if overrideService.Entrypoint != nil {
 | 
						|
		baseService.Entrypoint = overrideService.Entrypoint
 | 
						|
	}
 | 
						|
	if baseService.Environment != nil {
 | 
						|
		baseService.Environment.OverrideBy(overrideService.Environment)
 | 
						|
	} else {
 | 
						|
		baseService.Environment = overrideService.Environment
 | 
						|
	}
 | 
						|
	baseService.Expose = unique(baseService.Expose)
 | 
						|
	return baseService, nil
 | 
						|
}
 | 
						|
 | 
						|
func unique(slice []string) []string {
 | 
						|
	if slice == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	uniqMap := make(map[string]struct{})
 | 
						|
	var uniqSlice []string
 | 
						|
	for _, v := range slice {
 | 
						|
		if _, ok := uniqMap[v]; !ok {
 | 
						|
			uniqSlice = append(uniqSlice, v)
 | 
						|
			uniqMap[v] = struct{}{}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return uniqSlice
 | 
						|
}
 | 
						|
 | 
						|
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
 | 
						|
}
 | 
						|
 | 
						|
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
 | 
						|
}
 |