diff --git a/pkg/domain/dockerCompose/config.go b/pkg/domain/dockerCompose/config.go index ae463f6..e6c8b95 100644 --- a/pkg/domain/dockerCompose/config.go +++ b/pkg/domain/dockerCompose/config.go @@ -1,5 +1,18 @@ package dockerCompose +import ( + "fmt" + "strings" +) + +const ( + environmentDelimiter string = "=" + labelDelimiter string = "=" + volumeDelimiter string = ":" + portDelimiter string = ":" + portProtocolDelimiter string = "/" +) + type Config struct { Networks map[string]*Network `json:"networks,omitempty" yaml:"networks,omitempty"` Secrets map[string]*Secret `json:"secrets,omitempty" yaml:"secrets,omitempty"` @@ -396,11 +409,11 @@ func (s *Service) MergeLastWin(service *Service) { s.mergeLastWinExtraHosts(service.ExtraHosts) s.mergeLastWinImage(service.Image) s.mergeLastWinLabels(service.Labels) - s.mergeLastWinNetworks(s.Networks) - s.mergeLastWinPorts(s.Ports) - s.mergeLastWinSecrets(s.Secrets) - s.mergeLastWinULimits(s.ULimits) - s.mergeLastWinVolumes(s.Volumes) + s.mergeLastWinNetworks(service.Networks) + s.mergeLastWinPorts(service.Ports) + s.mergeLastWinSecrets(service.Secrets) + s.mergeLastWinULimits(service.ULimits) + s.mergeLastWinVolumes(service.Volumes) } } @@ -420,9 +433,34 @@ func (s *Service) mergeLastWinCapabilitiesDrop(capabilitiesDrop []string) { } } -func (s *Service) mergeLastWinDeploy(deploy *ServiceDeploy) {} +func (s *Service) mergeLastWinDeploy(deploy *ServiceDeploy) { + switch { + case s.Deploy == nil && deploy != nil: + s.Deploy = deploy + case s.Deploy != nil && deploy == nil: + fallthrough + case s.Deploy == nil && deploy == nil: + return + default: + s.Deploy.MergeLastWin(deploy) + } +} -func (s *Service) mergeLastWinEnvironments(environments []string) {} +func (s *Service) mergeLastWinEnvironments(environments []string) { + switch { + case s.Environments == nil && environments != nil: + s.Environments = environments + case s.Environments != nil && environments == nil: + fallthrough + case s.Environments == nil && environments == nil: + return + default: + for _, environment := range environments { + key, value := splitStringInKeyValue(environment, environmentDelimiter) + s.SetEnvironment(key, value) + } + } +} func (s *Service) mergeLastWinImage(image string) { if s.Image != image { @@ -438,12 +476,185 @@ func (s *Service) mergeLastWinExtraHosts(extraHosts []string) { } } -func (s *Service) mergeLastWinLabels(labels []string) {} -func (s *Service) mergeLastWinNetworks(networks map[string]*ServiceNetwork) {} -func (s *Service) mergeLastWinPorts(ports []string) {} -func (s *Service) mergeLastWinSecrets([]string) {} -func (s *Service) mergeLastWinULimits(uLimits *ServiceULimits) {} -func (s *Service) mergeLastWinVolumes([]string) {} +func (s *Service) mergeLastWinLabels(labels []string) { + switch { + case s.Labels == nil && labels != nil: + s.Labels = labels + case s.Labels != nil && labels == nil: + fallthrough + case s.Labels == nil && labels == nil: + return + default: + for _, label := range labels { + key, value := splitStringInKeyValue(label, labelDelimiter) + s.SetLabel(key, value) + } + } +} + +func (s *Service) mergeLastWinNetworks(networks map[string]*ServiceNetwork) { + switch { + case s.Networks == nil && networks != nil: + s.Networks = networks + case s.Networks != nil && networks == nil: + fallthrough + case s.Networks == nil && networks == nil: + return + default: + for name, network := range networks { + if _, exists := s.Networks[name]; exists { + s.Networks[name].MergeLastWin(network) + } else { + s.Networks[name] = network + } + } + } +} + +func (s *Service) mergeLastWinPorts(ports []string) { + switch { + case s.Ports == nil && ports != nil: + s.Ports = ports + case s.Ports != nil && ports == nil: + fallthrough + case s.Ports == nil && ports == nil: + return + default: + for _, port := range ports { + src, dest, protocol := splitStringInPort(port) + s.SetPort(src, dest, protocol) + } + } +} + +func (s *Service) mergeLastWinSecrets(secrets []string) { + for _, secret := range secrets { + if !existsInSlice(s.Secrets, secret) { + s.Secrets = append(s.Secrets, secret) + } + } +} + +func (s *Service) mergeLastWinULimits(uLimits *ServiceULimits) { + switch { + case s.ULimits == nil && uLimits != nil: + s.ULimits = uLimits + case s.ULimits != nil && uLimits == nil: + fallthrough + case s.ULimits == nil && uLimits == nil: + return + default: + s.ULimits.MergeLastWin(uLimits) + } +} + +func (s *Service) mergeLastWinVolumes(volumes []string) { + switch { + case s.Volumes == nil && volumes != nil: + s.Volumes = volumes + case s.Volumes != nil && volumes == nil: + fallthrough + case s.Volumes == nil && volumes == nil: + return + default: + for _, volume := range volumes { + src, dest, perm := splitStringInVolume(volume) + s.SetVolume(src, dest, perm) + } + } +} + +// RemoveEnvironment remove all found environment variable from the internal +// slice matching by the passed name. +func (s *Service) RemoveEnvironment(name string) { + environments := make([]string, 0) + for _, environment := range s.Environments { + key, value := splitStringInKeyValue(environment, environmentDelimiter) + if key != name { + environments = append(environments, fmt.Sprintf("%s%s%s", key, environmentDelimiter, value)) + } + } + s.Environments = environments +} + +// RemoveLabel remove all found labels from the internal slice matching by the +// passed name. +func (s *Service) RemoveLabel(name string) { + labels := make([]string, 0) + for _, label := range s.Labels { + key, value := splitStringInKeyValue(label, labelDelimiter) + if key != name { + labels = append(labels, fmt.Sprintf("%s%s%s", key, labelDelimiter, value)) + } + } + s.Labels = labels +} + +// RemovePort remove all found ports from the internal slice matching by the +// passed dest port. +func (s *Service) RemovePort(dest string) { + ports := make([]string, 0) + for _, port := range s.Ports { + srcPort, destPort, protocol := splitStringInPort(port) + + switch { + case destPort == dest && len(protocol) <= 0: + s.Ports = append(s.Ports, fmt.Sprintf("%s%s%s", srcPort, portDelimiter, destPort)) + case destPort == dest && len(protocol) > 0: + s.Ports = append(s.Ports, fmt.Sprintf("%s%s%s%s%s", srcPort, portDelimiter, destPort, portProtocolDelimiter, protocol)) + } + } + s.Ports = ports +} + +// RemoveVolume remove all found volumes from the internal slice matching by the +// dest path. +func (s *Service) RemoveVolume(dest string) { + volumes := make([]string, 0) + for _, volume := range s.Volumes { + srcPath, destPath, perm := splitStringInVolume(volume) + + switch { + case destPath == dest && len(perm) <= 0: + s.Volumes = append(s.Volumes, fmt.Sprintf("%s%s%s", srcPath, volumeDelimiter, destPath)) + case destPath == dest && len(perm) > 0: + s.Volumes = append(s.Volumes, fmt.Sprintf("%s%s%s%s%s", srcPath, volumeDelimiter, destPath, volumeDelimiter, perm)) + } + } + s.Volumes = volumes +} + +// SetEnvironment add or overwrite an existing environment variable. +func (s *Service) SetEnvironment(name string, value string) { + s.RemoveEnvironment(name) + s.Environments = append(s.Environments, fmt.Sprintf("%s%s%s", name, environmentDelimiter, value)) +} + +// SetLabel add or overwrite an existing label. +func (s *Service) SetLabel(name string, value string) { + s.RemoveLabel(name) + s.Labels = append(s.Labels, fmt.Sprintf("%s%s%s", name, labelDelimiter, value)) +} + +// SetPort add or overwrite an existing port. +func (s *Service) SetPort(src string, dest string, protocol string) { + s.RemovePort(dest) + if len(protocol) <= 0 { + s.Ports = append(s.Ports, fmt.Sprintf("%s%s%s", src, volumeDelimiter, dest)) + } else { + s.Ports = append(s.Ports, fmt.Sprintf("%s%s%s%s%s", src, portDelimiter, dest, portProtocolDelimiter, protocol)) + } +} + +// SetVolume add or overwrite an existing volume. +func (s *Service) SetVolume(src string, dest string, perm string) { + s.RemoveVolume(dest) + if len(perm) <= 0 { + s.Volumes = append(s.Volumes, fmt.Sprintf("%s%s%s", src, volumeDelimiter, dest)) + } else { + s.Volumes = append(s.Volumes, fmt.Sprintf("%s%s%s%s%s", src, volumeDelimiter, dest, volumeDelimiter, perm)) + } +} // NewService returns an empty initialized Service. func NewService() *Service { @@ -940,3 +1151,33 @@ func equalSlice[K comparable](sliceA []K, sliceB []K) bool { return equalFunc(sliceA, sliceB) && equalFunc(sliceB, sliceA) } + +func splitStringInKeyValue(s, sep string) (string, string) { + key := strings.Split(s, sep)[0] + value := strings.TrimPrefix(s, fmt.Sprintf("%s%s", key, sep)) + return key, value +} + +func splitStringInPort(s string) (string, string, string) { + parts := strings.Split(s, portDelimiter) + src := parts[0] + rest := parts[1] + + parts = strings.Split(rest, portProtocolDelimiter) + if len(parts) == 2 { + return src, parts[0], parts[1] + } + + return src, parts[0], "" +} + +func splitStringInVolume(s string) (string, string, string) { + parts := strings.Split(s, volumeDelimiter) + src := parts[0] + dest := parts[1] + if len(parts) == 3 && len(parts[2]) > 0 { + perm := parts[2] + return src, dest, perm + } + return src, dest, "" +} diff --git a/pkg/domain/dockerCompose/config_test.go b/pkg/domain/dockerCompose/config_test.go index 8179111..4fe949b 100644 --- a/pkg/domain/dockerCompose/config_test.go +++ b/pkg/domain/dockerCompose/config_test.go @@ -464,6 +464,761 @@ func TestService_Equal(t *testing.T) { } } +func TestService_MergeLastWin(t *testing.T) { + require := require.New(t) + + testCases := []struct { + serviceDeploymentA *dockerCompose.Service + serviceDeploymentB *dockerCompose.Service + expectedService *dockerCompose.Service + }{ + { + serviceDeploymentA: nil, + serviceDeploymentB: nil, + expectedService: nil, + }, + { + serviceDeploymentA: &dockerCompose.Service{}, + serviceDeploymentB: &dockerCompose.Service{}, + expectedService: &dockerCompose.Service{}, + }, + + // CapabilitiesAdd + { + serviceDeploymentA: &dockerCompose.Service{ + CapabilitiesAdd: []string{"NET_RAW"}, + }, + serviceDeploymentB: &dockerCompose.Service{ + CapabilitiesAdd: []string{}, + }, + expectedService: &dockerCompose.Service{ + CapabilitiesAdd: []string{"NET_RAW"}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + CapabilitiesAdd: []string{}, + }, + serviceDeploymentB: &dockerCompose.Service{ + CapabilitiesAdd: []string{"NET_RAW"}, + }, + expectedService: &dockerCompose.Service{ + CapabilitiesAdd: []string{"NET_RAW"}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + CapabilitiesAdd: []string{"NET_RAW"}, + }, + serviceDeploymentB: &dockerCompose.Service{ + CapabilitiesAdd: []string{"NET_RAW"}, + }, + expectedService: &dockerCompose.Service{ + CapabilitiesAdd: []string{"NET_RAW"}, + }, + }, + + // CapabilitiesDrop + { + serviceDeploymentA: &dockerCompose.Service{ + CapabilitiesDrop: []string{"NET_RAW"}, + }, + serviceDeploymentB: &dockerCompose.Service{ + CapabilitiesDrop: []string{}, + }, + expectedService: &dockerCompose.Service{ + CapabilitiesDrop: []string{"NET_RAW"}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + CapabilitiesDrop: []string{}, + }, + serviceDeploymentB: &dockerCompose.Service{ + CapabilitiesDrop: []string{"NET_RAW"}, + }, + expectedService: &dockerCompose.Service{ + CapabilitiesDrop: []string{"NET_RAW"}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + CapabilitiesDrop: []string{"NET_RAW"}, + }, + serviceDeploymentB: &dockerCompose.Service{ + CapabilitiesDrop: []string{"NET_RAW"}, + }, + expectedService: &dockerCompose.Service{ + CapabilitiesDrop: []string{"NET_RAW"}, + }, + }, + + // Deploy + { + serviceDeploymentA: &dockerCompose.Service{ + Deploy: nil, + }, + serviceDeploymentB: &dockerCompose.Service{ + Deploy: nil, + }, + expectedService: &dockerCompose.Service{ + Deploy: nil, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Deploy: dockerCompose.NewServiceDeploy(), + }, + serviceDeploymentB: &dockerCompose.Service{ + Deploy: nil, + }, + expectedService: &dockerCompose.Service{ + Deploy: dockerCompose.NewServiceDeploy(), + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Deploy: nil, + }, + serviceDeploymentB: &dockerCompose.Service{ + Deploy: dockerCompose.NewServiceDeploy(), + }, + expectedService: &dockerCompose.Service{ + Deploy: dockerCompose.NewServiceDeploy(), + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Deploy: dockerCompose.NewServiceDeploy(), + }, + serviceDeploymentB: &dockerCompose.Service{ + Deploy: dockerCompose.NewServiceDeploy(), + }, + expectedService: &dockerCompose.Service{ + Deploy: dockerCompose.NewServiceDeploy(), + }, + }, + + // Environments + { + serviceDeploymentA: &dockerCompose.Service{ + Environments: nil, + }, + serviceDeploymentB: &dockerCompose.Service{ + Environments: nil, + }, + expectedService: &dockerCompose.Service{ + Environments: nil, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Environments: []string{}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Environments: nil, + }, + expectedService: &dockerCompose.Service{ + Environments: []string{}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Environments: nil, + }, + serviceDeploymentB: &dockerCompose.Service{ + Environments: []string{}, + }, + expectedService: &dockerCompose.Service{ + Environments: []string{}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Environments: []string{}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Environments: []string{}, + }, + expectedService: &dockerCompose.Service{ + Environments: []string{}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Environments: []string{"PROXY_HOST=u.example.com"}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Environments: []string{"PROXY_HOST=u.example.local"}, + }, + expectedService: &dockerCompose.Service{ + Environments: []string{"PROXY_HOST=u.example.local"}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Environments: []string{"PROXY_HOST=u.example.com", "PROXY_HOST=u.example.de"}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Environments: []string{"PROXY_HOST=u.example.local"}, + }, + expectedService: &dockerCompose.Service{ + Environments: []string{"PROXY_HOST=u.example.local"}, + }, + }, + + // ExtraHosts + { + serviceDeploymentA: &dockerCompose.Service{ + ExtraHosts: nil, + }, + serviceDeploymentB: &dockerCompose.Service{ + ExtraHosts: nil, + }, + expectedService: &dockerCompose.Service{ + ExtraHosts: nil, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + ExtraHosts: []string{}, + }, + serviceDeploymentB: &dockerCompose.Service{ + ExtraHosts: nil, + }, + expectedService: &dockerCompose.Service{ + ExtraHosts: []string{}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + ExtraHosts: nil, + }, + serviceDeploymentB: &dockerCompose.Service{ + ExtraHosts: []string{}, + }, + expectedService: &dockerCompose.Service{ + ExtraHosts: []string{}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + ExtraHosts: []string{}, + }, + serviceDeploymentB: &dockerCompose.Service{ + ExtraHosts: []string{}, + }, + expectedService: &dockerCompose.Service{ + ExtraHosts: []string{}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + ExtraHosts: []string{"extra.host.local"}, + }, + serviceDeploymentB: &dockerCompose.Service{ + ExtraHosts: []string{"extra.host.local"}, + }, + expectedService: &dockerCompose.Service{ + ExtraHosts: []string{"extra.host.local"}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + ExtraHosts: []string{"extra.host.local"}, + }, + serviceDeploymentB: &dockerCompose.Service{ + ExtraHosts: []string{"extra.host.com"}, + }, + expectedService: &dockerCompose.Service{ + ExtraHosts: []string{"extra.host.com", "extra.host.local"}, + }, + }, + + // Labels + { + serviceDeploymentA: &dockerCompose.Service{ + Labels: nil, + }, + serviceDeploymentB: &dockerCompose.Service{ + Labels: nil, + }, + expectedService: &dockerCompose.Service{ + Labels: nil, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Labels: []string{}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Labels: nil, + }, + expectedService: &dockerCompose.Service{ + Labels: []string{}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Labels: nil, + }, + serviceDeploymentB: &dockerCompose.Service{ + Labels: []string{}, + }, + expectedService: &dockerCompose.Service{ + Labels: []string{}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Labels: []string{}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Labels: []string{}, + }, + expectedService: &dockerCompose.Service{ + Labels: []string{}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Labels: []string{"prometheus.io/scrape=true"}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Labels: []string{"prometheus.io/scrape=true"}, + }, + expectedService: &dockerCompose.Service{ + Labels: []string{"prometheus.io/scrape=true"}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Labels: []string{"prometheus.io/scrape=true", "prometheus.io/scrape=false"}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Labels: []string{"prometheus.io/scrape=true"}, + }, + expectedService: &dockerCompose.Service{ + Labels: []string{"prometheus.io/scrape=true"}, + }, + }, + + // Networks + { + serviceDeploymentA: &dockerCompose.Service{ + Networks: nil, + }, + serviceDeploymentB: &dockerCompose.Service{ + Networks: nil, + }, + expectedService: &dockerCompose.Service{ + Networks: nil, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Networks: make(map[string]*dockerCompose.ServiceNetwork), + }, + serviceDeploymentB: &dockerCompose.Service{ + Networks: nil, + }, + expectedService: &dockerCompose.Service{ + Networks: make(map[string]*dockerCompose.ServiceNetwork), + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Networks: nil, + }, + serviceDeploymentB: &dockerCompose.Service{ + Networks: make(map[string]*dockerCompose.ServiceNetwork), + }, + expectedService: &dockerCompose.Service{ + Networks: make(map[string]*dockerCompose.ServiceNetwork), + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Networks: make(map[string]*dockerCompose.ServiceNetwork), + }, + serviceDeploymentB: &dockerCompose.Service{ + Networks: make(map[string]*dockerCompose.ServiceNetwork), + }, + expectedService: &dockerCompose.Service{ + Networks: make(map[string]*dockerCompose.ServiceNetwork), + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Networks: map[string]*dockerCompose.ServiceNetwork{ + "proxy": {Aliases: []string{"app.proxy.network"}}, + }, + }, + serviceDeploymentB: &dockerCompose.Service{ + Networks: nil, + }, + expectedService: &dockerCompose.Service{ + Networks: map[string]*dockerCompose.ServiceNetwork{ + "proxy": {Aliases: []string{"app.proxy.network"}}, + }, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Networks: map[string]*dockerCompose.ServiceNetwork{ + "proxy": {Aliases: []string{"app.proxy.network"}}, + }, + }, + serviceDeploymentB: &dockerCompose.Service{ + Networks: map[string]*dockerCompose.ServiceNetwork{ + "db": {Aliases: []string{"app.db.network"}}, + }, + }, + expectedService: &dockerCompose.Service{ + Networks: map[string]*dockerCompose.ServiceNetwork{ + "db": {Aliases: []string{"app.db.network"}}, + "proxy": {Aliases: []string{"app.proxy.network"}}, + }, + }, + }, + + // Ports + { + serviceDeploymentA: &dockerCompose.Service{ + Ports: nil, + }, + serviceDeploymentB: &dockerCompose.Service{ + Ports: nil, + }, + expectedService: &dockerCompose.Service{ + Ports: nil, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Ports: []string{}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Ports: nil, + }, + expectedService: &dockerCompose.Service{ + Ports: []string{}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Ports: nil, + }, + serviceDeploymentB: &dockerCompose.Service{ + Ports: []string{}, + }, + expectedService: &dockerCompose.Service{ + Ports: []string{}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Ports: []string{}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Ports: []string{}, + }, + expectedService: &dockerCompose.Service{ + Ports: []string{}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Ports: []string{"80:80"}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Ports: []string{"80:80"}, + }, + expectedService: &dockerCompose.Service{ + Ports: []string{"80:80"}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Ports: []string{"80:80"}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Ports: []string{"10080:80"}, + }, + expectedService: &dockerCompose.Service{ + Ports: []string{"10080:80"}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Ports: []string{"80:80/tcp"}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Ports: []string{"80:80"}, + }, + expectedService: &dockerCompose.Service{ + Ports: []string{"80:80"}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Ports: []string{"80:80"}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Ports: []string{"10080:80/udp"}, + }, + expectedService: &dockerCompose.Service{ + Ports: []string{"10080:80/udp"}, + }, + }, + + // Secrets + { + serviceDeploymentA: &dockerCompose.Service{ + Secrets: nil, + }, + serviceDeploymentB: &dockerCompose.Service{ + Secrets: nil, + }, + expectedService: &dockerCompose.Service{ + Secrets: nil, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Secrets: []string{}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Secrets: nil, + }, + expectedService: &dockerCompose.Service{ + Secrets: []string{}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Secrets: nil, + }, + serviceDeploymentB: &dockerCompose.Service{ + Secrets: []string{}, + }, + expectedService: &dockerCompose.Service{ + Secrets: []string{}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Secrets: []string{}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Secrets: []string{}, + }, + expectedService: &dockerCompose.Service{ + Secrets: []string{}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Secrets: []string{"db_pass_credentials"}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Secrets: []string{"db_pass_credentials"}, + }, + expectedService: &dockerCompose.Service{ + Secrets: []string{"db_pass_credentials"}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Secrets: []string{"db_pass_credentials"}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Secrets: []string{"oauth2_pass_credentials"}, + }, + expectedService: &dockerCompose.Service{ + Secrets: []string{"db_pass_credentials", "oauth2_pass_credentials"}, + }, + }, + + // ULimits + { + serviceDeploymentA: &dockerCompose.Service{ + ULimits: nil, + }, + serviceDeploymentB: &dockerCompose.Service{ + ULimits: nil, + }, + expectedService: &dockerCompose.Service{ + ULimits: nil, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + ULimits: dockerCompose.NewServiceULimits(), + }, + serviceDeploymentB: &dockerCompose.Service{ + ULimits: nil, + }, + expectedService: &dockerCompose.Service{ + ULimits: dockerCompose.NewServiceULimits(), + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + ULimits: nil, + }, + serviceDeploymentB: &dockerCompose.Service{ + ULimits: dockerCompose.NewServiceULimits(), + }, + expectedService: &dockerCompose.Service{ + ULimits: dockerCompose.NewServiceULimits(), + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + ULimits: dockerCompose.NewServiceULimits(), + }, + serviceDeploymentB: &dockerCompose.Service{ + ULimits: dockerCompose.NewServiceULimits(), + }, + expectedService: &dockerCompose.Service{ + ULimits: dockerCompose.NewServiceULimits(), + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + ULimits: &dockerCompose.ServiceULimits{ + NProc: 10, + NoFile: &dockerCompose.ServiceULimitsNoFile{ + Hard: 10, + Soft: 10, + }, + }, + }, + serviceDeploymentB: &dockerCompose.Service{ + ULimits: &dockerCompose.ServiceULimits{ + NProc: 10, + NoFile: &dockerCompose.ServiceULimitsNoFile{ + Hard: 10, + Soft: 10, + }, + }, + }, + expectedService: &dockerCompose.Service{ + ULimits: &dockerCompose.ServiceULimits{ + NProc: 10, + NoFile: &dockerCompose.ServiceULimitsNoFile{ + Hard: 10, + Soft: 10, + }, + }, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + ULimits: &dockerCompose.ServiceULimits{ + NProc: 10, + NoFile: &dockerCompose.ServiceULimitsNoFile{ + Hard: 10, + Soft: 10, + }, + }, + }, + serviceDeploymentB: &dockerCompose.Service{ + ULimits: &dockerCompose.ServiceULimits{ + NProc: 15, + NoFile: &dockerCompose.ServiceULimitsNoFile{ + Hard: 25, + Soft: 20, + }, + }, + }, + expectedService: &dockerCompose.Service{ + ULimits: &dockerCompose.ServiceULimits{ + NProc: 15, + NoFile: &dockerCompose.ServiceULimitsNoFile{ + Hard: 25, + Soft: 20, + }, + }, + }, + }, + + // Volumes + { + serviceDeploymentA: &dockerCompose.Service{ + Volumes: nil, + }, + serviceDeploymentB: &dockerCompose.Service{ + Volumes: nil, + }, + expectedService: &dockerCompose.Service{ + Volumes: nil, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Volumes: []string{}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Volumes: nil, + }, + expectedService: &dockerCompose.Service{ + Volumes: []string{}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Volumes: nil, + }, + serviceDeploymentB: &dockerCompose.Service{ + Volumes: []string{}, + }, + expectedService: &dockerCompose.Service{ + Volumes: []string{}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Volumes: []string{}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Volumes: []string{}, + }, + expectedService: &dockerCompose.Service{ + Volumes: []string{}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Volumes: []string{"/etc/localtime:/etc/localtime"}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Volumes: []string{"/etc/localtime:/etc/localtime"}, + }, + expectedService: &dockerCompose.Service{ + Volumes: []string{"/etc/localtime:/etc/localtime"}, + }, + }, + { + serviceDeploymentA: &dockerCompose.Service{ + Volumes: []string{"/etc/localtime:/etc/localtime"}, + }, + serviceDeploymentB: &dockerCompose.Service{ + Volumes: []string{"/usr/share/zoneinfo/Europe/Berlin:/etc/localtime"}, + }, + expectedService: &dockerCompose.Service{ + Volumes: []string{"/usr/share/zoneinfo/Europe/Berlin:/etc/localtime"}, + }, + }, + } + + for i, testCase := range testCases { + testCase.serviceDeploymentA.MergeLastWin(testCase.serviceDeploymentB) + require.True(testCase.expectedService.Equal(testCase.serviceDeploymentA), "Failed test case %v", i) + } +} + func TestSecretDeploy_Equal(t *testing.T) { require := require.New(t)