diff --git a/go.mod b/go.mod index b999b4e..6d48064 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,11 @@ module git.cryptic.systems/volker.raschek/dcmerge go 1.20 + +require github.com/stretchr/testify v1.8.4 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fa4b6e6 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pki/domain/dockerCompose/config.go b/pki/domain/dockerCompose/config.go index 086b773..05dcffb 100644 --- a/pki/domain/dockerCompose/config.go +++ b/pki/domain/dockerCompose/config.go @@ -53,11 +53,37 @@ func (c *Config) ExistsVolume(name string) bool { // MergeLastWin merges a config and overwrite already existing properties func (c *Config) MergeLastWin(config *Config) { - if c.Version != config.Version { - c.Version = config.Version - } + switch { + case c == nil && config == nil: + fallthrough + case c != nil && config == nil: + return - for networkName, network := range c.Networks { + // WARN: It's not possible to change the memory pointer c *Config + // to a new initialized config without returning the Config + // it self. + // + // case c == nil && config != nil: + // c = NewConfig() + // fallthrough + + default: + c.mergeLastWinNetworks(config.Networks) + c.mergeLastWinSecrets(config.Secrets) + c.mergeLastWinServices(config.Services) + c.mergeLastWinVersion(config.Version) + c.mergeLastWinVolumes(config.Volumes) + } +} + +func (c *Config) mergeLastWinVersion(version string) { + if c.Version != version { + c.Version = version + } +} + +func (c *Config) mergeLastWinNetworks(networks map[string]*Network) { + for networkName, network := range networks { if network == nil { continue } @@ -68,20 +94,24 @@ func (c *Config) MergeLastWin(config *Config) { c.Networks[networkName] = network } } +} - for secretName, secret := range c.Secrets { +func (c *Config) mergeLastWinSecrets(secrets map[string]*Secret) { + for secretName, secret := range secrets { if secret == nil { continue } - if c.ExistsSecret(secretName) { + if c.ExistsNetwork(secretName) { c.Secrets[secretName].MergeLastWin(secret) } else { c.Secrets[secretName] = secret } } +} - for serviceName, service := range c.Services { +func (c *Config) mergeLastWinServices(services map[string]*Service) { + for serviceName, service := range services { if service == nil { continue } @@ -92,18 +122,20 @@ func (c *Config) MergeLastWin(config *Config) { c.Services[serviceName] = service } } +} - // for volumeName, volume := range c.Volumes { - // if volume == nil { - // continue - // } +func (c *Config) mergeLastWinVolumes(volumes map[string]*Volume) { + for volumeName, volume := range volumes { + if volume == nil { + continue + } - // if c.ExistsVolume(volumeName) { - // c.Volumes[volumeName].MergeLastWin(volume) - // } else { - // c.Volumes[volumeName] = volume - // } - // } + if c.ExistsNetwork(volumeName) { + c.Volumes[volumeName].MergeLastWin(volume) + } else { + c.Volumes[volumeName] = volume + } + } } func NewConfig() *Config { @@ -143,13 +175,40 @@ func (n *Network) Equal(equalable Equalable) bool { } func (n *Network) MergeLastWin(network *Network) { - if !n.IPAM.Equal(network.IPAM) { - n.IPAM = network.IPAM + switch { + case n == nil && network == nil: + fallthrough + case n != nil && network == nil: + return + + // WARN: It's not possible to change the memory pointer n *Network + // to a new initialized network without returning the Network + // it self. + // + // case n == nil && network != nil: + // c = NewCNetwork() + // fallthrough + + default: + n.mergeLastWinIPAM(network.IPAM) + } +} + +func (n *Network) mergeLastWinIPAM(networkIPAM *NetworkIPAM) { + if !n.IPAM.Equal(networkIPAM) { + n.IPAM.MergeLastWin(networkIPAM) + } +} + +func NewNetwork() *Network { + return &Network{ + External: false, + IPAM: new(NetworkIPAM), } } type NetworkIPAM struct { - Config []*NetworkIPAMConfig `json:"config,omitempty" yaml:"config,omitempty"` + Configs []*NetworkIPAMConfig `json:"config,omitempty" yaml:"config,omitempty"` } // Equal returns true if the passed equalable is equal @@ -167,7 +226,41 @@ func (nIPAM *NetworkIPAM) Equal(equalable Equalable) bool { case nIPAM == nil && networkIPAM != nil: return false default: - return Equal(nIPAM.Config, networkIPAM.Config) + return Equal(nIPAM.Configs, networkIPAM.Configs) + } +} + +func (nIPAM *NetworkIPAM) MergeLastWin(networkIPAM *NetworkIPAM) { + switch { + case nIPAM == nil && networkIPAM == nil: + fallthrough + case nIPAM != nil && networkIPAM == nil: + return + + // WARN: It's not possible to change the memory pointer n *NetworkIPAM + // to a new initialized networkIPAM without returning the NetworkIPAM + // it self. + // + // case nIPAM == nil && networkIPAM != nil: + // c = NewNetworkIPAM() + // fallthrough + + default: + nIPAM.mergeLastWinConfig(networkIPAM.Configs) + } +} + +func (nIPAM *NetworkIPAM) mergeLastWinConfig(networkIPAMConfigs []*NetworkIPAMConfig) { + for _, networkIPAMConfig := range networkIPAMConfigs { + if !existsInSlice(nIPAM.Configs, networkIPAMConfig) { + nIPAM.Configs = append(nIPAM.Configs, networkIPAMConfig) + } + } +} + +func NewNetworkIPAM() *NetworkIPAM { + return &NetworkIPAM{ + Configs: make([]*NetworkIPAMConfig, 0), } } @@ -194,6 +287,10 @@ func (nIPAMConfig *NetworkIPAMConfig) Equal(equalable Equalable) bool { } } +func NewNetworkIPAMConfig() *NetworkIPAMConfig { + return &NetworkIPAMConfig{} +} + type Secret struct { File string `json:"file,omitempty" yaml:"file,omitempty"` } @@ -217,12 +314,18 @@ func (s *Secret) Equal(equalable Equalable) bool { } } +// MergeLastWin merges adds or overwrite the attributes of the passed secret +// with the existing one. func (s *Secret) MergeLastWin(secret *Secret) { if !s.Equal(secret) { s.File = secret.File } } +func NewSecret() *Secret { + return &Secret{} +} + type Service struct { CapabilitiesAdd []string `json:"cap_add,omitempty" yaml:"cap_add,omitempty"` CapabilitiesDrop []string `json:"cap_drop,omitempty" yaml:"cap_drop,omitempty"` @@ -268,26 +371,151 @@ func (s *Service) Equal(equalable Equalable) bool { } } +// MergeLastWin merges adds or overwrite the attributes of the passed secret +// with the existing one. +func (s *Service) MergeLastWin(service *Service) { + switch { + case s == nil && service == nil: + fallthrough + case s != nil && service == nil: + return + + // WARN: It's not possible to change the memory pointer s *Service + // to a new initialized service without returning the Service + // it self. + // + // case s == nil && service != nil: + // s = NewService() + // fallthrough + + default: + s.mergeLastWinCapabilitiesAdd(service.CapabilitiesAdd) + s.mergeLastWinCapabilitiesDrop(service.CapabilitiesDrop) + s.mergeLastWinDeploy(service.Deploy) + s.mergeLastWinEnvironments(service.Environments) + 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) + } +} + +func (s *Service) mergeLastWinCapabilitiesAdd(capabilitiesAdd []string) { + for _, capabilityAdd := range capabilitiesAdd { + if !existsInSlice(s.CapabilitiesAdd, capabilityAdd) { + s.CapabilitiesAdd = append(s.CapabilitiesAdd, capabilityAdd) + } + } +} + +func (s *Service) mergeLastWinCapabilitiesDrop(capabilitiesDrop []string) { + for _, capabilityDrop := range capabilitiesDrop { + if !existsInSlice(s.CapabilitiesAdd, capabilityDrop) { + s.CapabilitiesDrop = append(s.CapabilitiesDrop, capabilityDrop) + } + } +} + +func (s *Service) mergeLastWinDeploy(deploy *ServiceDeploy) {} + +func (s *Service) mergeLastWinEnvironments(environments []string) {} + +func (s *Service) mergeLastWinImage(image string) { + if s.Image != image { + s.Image = image + } +} + +func (s *Service) mergeLastWinExtraHosts(extraHosts []string) { + for _, extraHost := range extraHosts { + if !existsInSlice(s.ExtraHosts, extraHost) { + s.ExtraHosts = append(s.ExtraHosts, extraHost) + } + } +} + +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) {} + +// NewService returns an empty initialized Service. +func NewService() *Service { + return &Service{ + CapabilitiesAdd: make([]string, 0), + CapabilitiesDrop: make([]string, 0), + Deploy: new(ServiceDeploy), + Environments: make([]string, 0), + ExtraHosts: make([]string, 0), + Labels: make([]string, 0), + Networks: make(map[string]*ServiceNetwork), + Ports: make([]string, 0), + Secrets: make([]string, 0), + ULimits: new(ServiceULimits), + Volumes: make([]string, 0), + } +} + type ServiceDeploy struct { Resources *ServiceDeployResources `json:"resources" yaml:"resources"` } // Equal returns true if the passed equalable is equal func (sd *ServiceDeploy) Equal(equalable Equalable) bool { - serviceDeployment, ok := equalable.(*ServiceDeploy) + serviceDeploy, ok := equalable.(*ServiceDeploy) if !ok { return false } switch { - case sd == nil && serviceDeployment == nil: + case sd == nil && serviceDeploy == nil: return true - case sd != nil && serviceDeployment == nil: + case sd != nil && serviceDeploy == nil: fallthrough - case sd == nil && serviceDeployment != nil: + case sd == nil && serviceDeploy != nil: return false default: - return sd.Resources.Equal(serviceDeployment.Resources) + return sd.Resources.Equal(serviceDeploy.Resources) + } +} + +// MergeLastWin merges adds or overwrite the attributes of the passed +// serviceDeploy with the existing one. +func (sd *ServiceDeploy) MergeLastWin(serviceDeploy *ServiceDeploy) { + switch { + case sd == nil && serviceDeploy == nil: + fallthrough + case sd != nil && serviceDeploy == nil: + return + + // WARN: It's not possible to change the memory pointer sd *ServiceDeploy + // to a new initialized serviceDeploy without returning the ServiceDeploy + // it self. + // + // case sd == nil && serviceDeploy != nil: + // sd = NewServiceDeploy() + // fallthrough + + default: + sd.mergeLastWinDeployResources(serviceDeploy.Resources) + } +} + +func (sd *ServiceDeploy) mergeLastWinDeployResources(resources *ServiceDeployResources) { + if !sd.Resources.Equal(resources) { + sd.Resources.MergeLastWin(resources) + } +} + +func NewServiceDeploy() *ServiceDeploy { + return &ServiceDeploy{ + Resources: new(ServiceDeployResources), } } @@ -298,21 +526,61 @@ type ServiceDeployResources struct { // Equal returns true if the passed equalable is equal func (sdr *ServiceDeployResources) Equal(equalable Equalable) bool { - serviceDeploymentResources, ok := equalable.(*ServiceDeployResources) + serviceDeployResources, ok := equalable.(*ServiceDeployResources) if !ok { return false } switch { - case sdr == nil && serviceDeploymentResources == nil: + case sdr == nil && serviceDeployResources == nil: return true - case sdr != nil && serviceDeploymentResources == nil: + case sdr != nil && serviceDeployResources == nil: fallthrough - case sdr == nil && serviceDeploymentResources != nil: + case sdr == nil && serviceDeployResources != nil: return false default: - return sdr.Limits.Equal(serviceDeploymentResources.Limits) && - sdr.Reservations.Equal(serviceDeploymentResources) + return sdr.Limits.Equal(serviceDeployResources.Limits) && + sdr.Reservations.Equal(serviceDeployResources) + } +} + +// MergeLastWin merges adds or overwrite the attributes of the passed +// serviceDeployResources with the existing one. +func (sdr *ServiceDeployResources) MergeLastWin(serviceDeployResources *ServiceDeployResources) { + switch { + case sdr == nil && serviceDeployResources == nil: + fallthrough + case sdr != nil && serviceDeployResources == nil: + return + + // WARN: It's not possible to change the memory pointer sdr *ServiceDeployResources + // to a new initialized serviceDeployResources without returning the + // serviceDeployResources it self. + case sdr == nil && serviceDeployResources != nil: + sdr = NewServiceDeployResources() + fallthrough + default: + sdr.mergeLastWinLimits(serviceDeployResources.Limits) + sdr.mergeLastWinReservations(serviceDeployResources.Reservations) + } +} + +func (sdr *ServiceDeployResources) mergeLastWinLimits(limits *ServiceDeployResourcesLimits) { + if !sdr.Limits.Equal(limits) { + sdr.Limits.MergeLastWin(limits) + } +} + +func (sdr *ServiceDeployResources) mergeLastWinReservations(reservations *ServiceDeployResourcesLimits) { + if !sdr.Reservations.Equal(reservations) { + sdr.Reservations.MergeLastWin(reservations) + } +} + +func NewServiceDeployResources() *ServiceDeployResources { + return &ServiceDeployResources{ + Limits: new(ServiceDeployResourcesLimits), + Reservations: new(ServiceDeployResourcesLimits), } } @@ -323,24 +591,63 @@ type ServiceDeployResourcesLimits struct { // Equal returns true if the passed equalable is equal func (sdrl *ServiceDeployResourcesLimits) Equal(equalable Equalable) bool { - serviceDeploymentResourcesLimits, ok := equalable.(*ServiceDeployResourcesLimits) + serviceDeployResourcesLimits, ok := equalable.(*ServiceDeployResourcesLimits) if !ok { return false } switch { - case sdrl == nil && serviceDeploymentResourcesLimits == nil: + case sdrl == nil && serviceDeployResourcesLimits == nil: return true - case sdrl != nil && serviceDeploymentResourcesLimits == nil: + case sdrl != nil && serviceDeployResourcesLimits == nil: fallthrough - case sdrl == nil && serviceDeploymentResourcesLimits != nil: + case sdrl == nil && serviceDeployResourcesLimits != nil: return false default: - return sdrl.CPUs == serviceDeploymentResourcesLimits.CPUs && - sdrl.Memory == serviceDeploymentResourcesLimits.Memory + return sdrl.CPUs == serviceDeployResourcesLimits.CPUs && + sdrl.Memory == serviceDeployResourcesLimits.Memory } } +// MergeLastWin merges adds or overwrite the attributes of the passed +// serviceDeployResourcesLimits with the existing one. +func (sdrl *ServiceDeployResourcesLimits) MergeLastWin(serviceDeployResourcesLimits *ServiceDeployResourcesLimits) { + switch { + case sdrl == nil && serviceDeployResourcesLimits == nil: + fallthrough + case sdrl != nil && serviceDeployResourcesLimits == nil: + return + + // WARN: It's not possible to change the memory pointer sdrl *ServiceDeployResourcesLimits + // to a new initialized serviceDeployResourcesLimits without returning the + // serviceDeployResourcesLimits it self. + // + // case sdrl == nil && serviceDeployResourcesLimits != nil: + // sdrl = NewServiceDeployResourcesLimits() + // fallthrough + + default: + sdrl.mergeLastWinCPUs(serviceDeployResourcesLimits.CPUs) + sdrl.mergeLastWinMemory(serviceDeployResourcesLimits.Memory) + } +} + +func (sdrl *ServiceDeployResourcesLimits) mergeLastWinCPUs(cpus string) { + if sdrl.CPUs != cpus { + sdrl.CPUs = cpus + } +} + +func (sdrl *ServiceDeployResourcesLimits) mergeLastWinMemory(memory string) { + if sdrl.Memory != memory { + sdrl.Memory = memory + } +} + +func NewServiceDeployResourcesLimits() *ServiceDeployResourcesLimits { + return &ServiceDeployResourcesLimits{} +} + type ServiceNetwork struct { Aliases []string `json:"aliases,omitempty" yaml:"aliases,omitempty"` } @@ -364,6 +671,44 @@ func (sn *ServiceNetwork) Equal(equalable Equalable) bool { } } +// MergeLastWin merges adds or overwrite the attributes of the passed +// serviceNetwork with the existing one. +func (sn *ServiceNetwork) MergeLastWin(serviceNetwork *ServiceNetwork) { + switch { + case sn == nil && serviceNetwork == nil: + fallthrough + case sn != nil && serviceNetwork == nil: + return + + // WARN: It's not possible to change the memory pointer sn *ServiceNetwork to a new + // initialized ServiceNetwork without returning the serviceNetwork it self. + // + // case l == nil && serviceULimits != nil: + // l = NewServiceULimits() + // fallthrough + + case sn == nil && serviceNetwork != nil: + sn = NewServiceNetwork() + fallthrough + default: + sn.mergeLastWinAliases(serviceNetwork.Aliases) + } +} + +func (sn *ServiceNetwork) mergeLastWinAliases(aliases []string) { + for _, alias := range aliases { + if !existsInSlice(sn.Aliases, alias) { + sn.Aliases = append(sn.Aliases, alias) + } + } +} + +func NewServiceNetwork() *ServiceNetwork { + return &ServiceNetwork{ + Aliases: make([]string, 0), + } +} + type ServiceULimits struct { NProc uint `json:"nproc,omitempty" yaml:"nproc,omitempty"` NoFile *ServiceULimitsNoFile `json:"nofile,omitempty" yaml:"nofile,omitempty"` @@ -389,6 +734,46 @@ func (l *ServiceULimits) Equal(equalable Equalable) bool { } } +// MergeLastWin merges adds or overwrite the attributes of the passed +// ServiceULimits with the existing one. +func (l *ServiceULimits) MergeLastWin(serviceULimits *ServiceULimits) { + switch { + case l == nil && serviceULimits == nil: + fallthrough + case l != nil && serviceULimits == nil: + return + + // WARN: It's not possible to change the memory pointer l *ServiceULimits to a new + // initialized ServiceULimits without returning the serviceULimits it self. + // + // case l == nil && serviceULimits != nil: + // l = NewServiceULimits() + // fallthrough + + default: + l.mergeLastWinNProc(l.NProc) + l.mergeLastWinNoFile(serviceULimits.NoFile) + } +} + +func (l *ServiceULimits) mergeLastWinNProc(nproc uint) { + if l.NProc != nproc { + l.NProc = nproc + } +} + +func (l *ServiceULimits) mergeLastWinNoFile(noFile *ServiceULimitsNoFile) { + if !l.NoFile.Equal(noFile) { + l.NoFile.MergeLastWin(noFile) + } +} + +func NewServiceULimits() *ServiceULimits { + return &ServiceULimits{ + NoFile: new(ServiceULimitsNoFile), + } +} + type ServiceULimitsNoFile struct { Hard uint `json:"hard" yaml:"hard"` Soft uint `json:"soft" yaml:"soft"` @@ -414,6 +799,45 @@ func (nf *ServiceULimitsNoFile) Equal(equalable Equalable) bool { } } +// MergeLastWin merges adds or overwrite the attributes of the passed +// ServiceULimits with the existing one. +func (nf *ServiceULimitsNoFile) MergeLastWin(serviceULimitsNoFile *ServiceULimitsNoFile) { + switch { + case nf == nil && serviceULimitsNoFile == nil: + fallthrough + case nf != nil && serviceULimitsNoFile == nil: + return + + // WARN: It's not possible to change the memory pointer nf *ServiceULimitsNoFile + // to a new initialized ServiceULimitsNoFile without returning the serviceULimitsNoFile + // it self. + // + // case nf == nil && serviceULimitsNoFile != nil: + // nf = NewServiceULimitsNoFile() + // fallthrough + + default: + nf.mergeLastWinHard(serviceULimitsNoFile.Hard) + nf.mergeLastWinSoft(serviceULimitsNoFile.Soft) + } +} + +func (nf *ServiceULimitsNoFile) mergeLastWinHard(hard uint) { + if nf.Hard != hard { + nf.Hard = hard + } +} + +func (nf *ServiceULimitsNoFile) mergeLastWinSoft(soft uint) { + if nf.Soft != soft { + nf.Soft = soft + } +} + +func NewServiceULimitsNoFile() *ServiceULimitsNoFile { + return &ServiceULimitsNoFile{} +} + type Volume struct { External bool `json:"external,omitempty" yaml:"external,omitempty"` } @@ -437,6 +861,48 @@ func (v *Volume) Equal(equalable Equalable) bool { } } +func (v *Volume) MergeLastWin(volume *Volume) { + switch { + case v == nil && volume == nil: + fallthrough + case v != nil && volume == nil: + return + + // WARN: It's not possible to change the memory pointer v *Volume to a new + // initialized Volume without returning the volume it self. + // + // case v == nil && volume != nil: + // v = NewVolume() + // fallthrough + + default: + v.mergeLastWinExternal(volume.External) + } +} + +func (v *Volume) mergeLastWinExternal(external bool) { + if v.External != external { + v.External = external + } +} + +func NewVolume() *Volume { + return &Volume{ + External: false, + } +} + +// existsInSlice returns true when the passed comparable K exists in slice of +// comparables []K. +func existsInSlice[K comparable](comparables []K, k K) bool { + for _, c := range comparables { + if c == k { + return true + } + } + return false +} + func equalSlice[K comparable](sliceA []K, sliceB []K) bool { equalFunc := func(sliceA []K, sliceB []K) bool { LOOP: diff --git a/pki/domain/dockerCompose/config_test.go b/pki/domain/dockerCompose/config_test.go new file mode 100644 index 0000000..e4951e3 --- /dev/null +++ b/pki/domain/dockerCompose/config_test.go @@ -0,0 +1,141 @@ +package dockerCompose_test + +import ( + "testing" + + "git.cryptic.systems/volker.raschek/dcmerge/pki/domain/dockerCompose" + "github.com/stretchr/testify/require" +) + +func TestServiceDeployResourcesLimits_EqualAndMergeLastWin(t *testing.T) { + require := require.New(t) + + testCases := []struct { + equalableA dockerCompose.Equalable + equalableB dockerCompose.Equalable + expectedResult bool + }{ + { + equalableA: nil, + equalableB: nil, + expectedResult: true, + }, + { + equalableA: &dockerCompose.ServiceDeployResourcesLimits{ + CPUs: "1", + Memory: "500", + }, + equalableB: &dockerCompose.NetworkIPAM{}, + expectedResult: false, + }, + { + equalableA: &dockerCompose.ServiceDeployResourcesLimits{ + CPUs: "1", + Memory: "500", + }, + equalableB: nil, + expectedResult: false, + }, + { + equalableA: &dockerCompose.ServiceDeployResourcesLimits{ + CPUs: "1", + Memory: "500", + }, + equalableB: &dockerCompose.ServiceDeployResourcesLimits{ + CPUs: "1", + Memory: "500", + }, + expectedResult: true, + }, + { + equalableA: &dockerCompose.ServiceDeployResourcesLimits{ + CPUs: "1", + }, + equalableB: &dockerCompose.ServiceDeployResourcesLimits{ + CPUs: "2", + }, + expectedResult: false, + }, + { + equalableA: &dockerCompose.ServiceDeployResourcesLimits{ + Memory: "500", + }, + equalableB: &dockerCompose.ServiceDeployResourcesLimits{ + Memory: "1000", + }, + expectedResult: false, + }, + } + + for i, testCase := range testCases { + require.Equal(testCase.expectedResult, testCase.equalableA.Equal(testCase.equalableB), "Failed test case %v", i) + } +} + +func TestServiceDeployResourcesLimits_MergeLastWin(t *testing.T) { + require := require.New(t) + + testCases := []struct { + serviceDeploymentResourcesLimitsA *dockerCompose.ServiceDeployResourcesLimits + serviceDeploymentResourcesLimitsB *dockerCompose.ServiceDeployResourcesLimits + expectedResult *dockerCompose.ServiceDeployResourcesLimits + }{ + { + serviceDeploymentResourcesLimitsA: nil, + serviceDeploymentResourcesLimitsB: nil, + expectedResult: nil, + }, + { + serviceDeploymentResourcesLimitsA: &dockerCompose.ServiceDeployResourcesLimits{ + CPUs: "1", + Memory: "500", + }, + serviceDeploymentResourcesLimitsB: nil, + expectedResult: &dockerCompose.ServiceDeployResourcesLimits{ + CPUs: "1", + Memory: "500", + }, + }, + { + serviceDeploymentResourcesLimitsA: &dockerCompose.ServiceDeployResourcesLimits{ + CPUs: "1", + Memory: "500", + }, + serviceDeploymentResourcesLimitsB: &dockerCompose.ServiceDeployResourcesLimits{ + CPUs: "1", + Memory: "500", + }, + expectedResult: &dockerCompose.ServiceDeployResourcesLimits{ + CPUs: "1", + Memory: "500", + }, + }, + { + serviceDeploymentResourcesLimitsA: &dockerCompose.ServiceDeployResourcesLimits{ + CPUs: "1", + }, + serviceDeploymentResourcesLimitsB: &dockerCompose.ServiceDeployResourcesLimits{ + CPUs: "2", + }, + expectedResult: &dockerCompose.ServiceDeployResourcesLimits{ + CPUs: "2", + }, + }, + { + serviceDeploymentResourcesLimitsA: &dockerCompose.ServiceDeployResourcesLimits{ + Memory: "500", + }, + serviceDeploymentResourcesLimitsB: &dockerCompose.ServiceDeployResourcesLimits{ + Memory: "1000", + }, + expectedResult: &dockerCompose.ServiceDeployResourcesLimits{ + Memory: "1000", + }, + }, + } + + for i, testCase := range testCases { + testCase.serviceDeploymentResourcesLimitsA.MergeLastWin(testCase.serviceDeploymentResourcesLimitsB) + require.True(testCase.expectedResult.Equal(testCase.serviceDeploymentResourcesLimitsA), "Failed test case %v", i) + } +}