diff --git a/cmd/root.go b/cmd/root.go index e69de29..1d619dd 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -0,0 +1 @@ +package cmd diff --git a/pki/domain/dockerCompose/config.go b/pki/domain/dockerCompose/config.go index a533654..086b773 100644 --- a/pki/domain/dockerCompose/config.go +++ b/pki/domain/dockerCompose/config.go @@ -8,24 +8,47 @@ type Config struct { Volumes map[string]*Volume `json:"volumes,omitempty" yaml:"volumes,omitempty"` } +// Equal returns true if the passed equalable is equal +func (c *Config) Equal(equalable Equalable) bool { + config, ok := equalable.(*Config) + if !ok { + return false + } + + switch { + case c == nil && config == nil: + return true + case c != nil && config == nil: + fallthrough + case c == nil && config != nil: + return false + default: + return EqualStringMap(c.Networks, config.Networks) && + EqualStringMap(c.Secrets, config.Secrets) && + EqualStringMap(c.Services, config.Services) && + c.Version == config.Version && + EqualStringMap(c.Volumes, config.Volumes) + } +} + // ExistsNetwork returns true if a network with the passed named exists. func (c *Config) ExistsNetwork(name string) bool { - return existsKeyInMap(c.Networks, name) + return ExistsInMap(c.Networks, name) } // ExistsSecret returns true if a secret with the passed named exists. func (c *Config) ExistsSecret(name string) bool { - return existsKeyInMap(c.Secrets, name) + return ExistsInMap(c.Secrets, name) } // ExistsService returns true if a service with the passed named exists. func (c *Config) ExistsService(name string) bool { - return existsKeyInMap(c.Services, name) + return ExistsInMap(c.Services, name) } // ExistsVolumes returns true if a volume with the passed named exists. func (c *Config) ExistsVolume(name string) bool { - return existsKeyInMap(c.Volumes, name) + return ExistsInMap(c.Volumes, name) } // MergeLastWin merges a config and overwrite already existing properties @@ -46,29 +69,29 @@ func (c *Config) MergeLastWin(config *Config) { } } - // for secretName, secret := range c.Secrets { - // if secret == nil { - // continue - // } + for secretName, secret := range c.Secrets { + if secret == nil { + continue + } - // if c.ExistsSecret(secretName) { - // c.Secrets[secretName].MergeLastWin(secret) - // } else { - // c.Secrets[secretName] = secret - // } - // } + if c.ExistsSecret(secretName) { + c.Secrets[secretName].MergeLastWin(secret) + } else { + c.Secrets[secretName] = secret + } + } - // for serviceName, service := range c.Services { - // if service == nil { - // continue - // } + for serviceName, service := range c.Services { + if service == nil { + continue + } - // if c.ExistsService(serviceName) { - // c.Services[serviceName].MergeLastWin(service) - // } else { - // c.Services[serviceName] = service - // } - // } + if c.ExistsService(serviceName) { + c.Services[serviceName].MergeLastWin(service) + } else { + c.Services[serviceName] = service + } + } // for volumeName, volume := range c.Volumes { // if volume == nil { @@ -98,38 +121,108 @@ type Network struct { IPAM *NetworkIPAM `json:"ipam,omitempty" yaml:"ipam,omitempty"` } -// MergeLastWin merges a network and overwrite already existing properties +// Equal returns true if the passed equalable is equal +func (n *Network) Equal(equalable Equalable) bool { + network, ok := equalable.(*Network) + if !ok { + return false + } + + switch { + case n == nil && network == nil: + return true + case n != nil && network == nil: + fallthrough + case n == nil && network != nil: + return false + default: + return n.External == network.External && + n.Driver == network.Driver && + n.IPAM.Equal(network.IPAM) + } +} + func (n *Network) MergeLastWin(network *Network) { - if n.External != network.External { - n.External = network.External + if !n.IPAM.Equal(network.IPAM) { + n.IPAM = network.IPAM } - - if n.Driver != network.Driver { - n.Driver = network.Driver - } - - // n.IPAM.MergeLastWin(network.IPAM.Config) } type NetworkIPAM struct { Config []*NetworkIPAMConfig `json:"config,omitempty" yaml:"config,omitempty"` } -func (nipam *NetworkIPAM) Merge(networkIPAM *NetworkIPAM) { - if networkIPAM == nil { - return +// Equal returns true if the passed equalable is equal +func (nIPAM *NetworkIPAM) Equal(equalable Equalable) bool { + networkIPAM, ok := equalable.(*NetworkIPAM) + if !ok { + return false } + switch { + case nIPAM == nil && networkIPAM == nil: + return true + case nIPAM != nil && networkIPAM == nil: + fallthrough + case nIPAM == nil && networkIPAM != nil: + return false + default: + return Equal(nIPAM.Config, networkIPAM.Config) + } } type NetworkIPAMConfig struct { Subnet string `json:"subnet,omitempty" yaml:"subnet,omitempty"` } +// Equal returns true if the passed equalable is equal +func (nIPAMConfig *NetworkIPAMConfig) Equal(equalable Equalable) bool { + networkIPAMConfig, ok := equalable.(*NetworkIPAMConfig) + if !ok { + return false + } + + switch { + case nIPAMConfig == nil && networkIPAMConfig == nil: + return true + case nIPAMConfig != nil && networkIPAMConfig == nil: + fallthrough + case nIPAMConfig == nil && networkIPAMConfig != nil: + return false + default: + return nIPAMConfig.Subnet == networkIPAMConfig.Subnet + } +} + type Secret struct { File string `json:"file,omitempty" yaml:"file,omitempty"` } +// Equal returns true if the passed equalable is equal +func (s *Secret) Equal(equalable Equalable) bool { + secret, ok := equalable.(*Secret) + if !ok { + return false + } + + switch { + case s == nil && secret == nil: + return true + case s != nil && secret == nil: + fallthrough + case s == nil && secret != nil: + return false + default: + return s.File == secret.File + } +} + +func (s *Secret) MergeLastWin(secret *Secret) { + if !s.Equal(secret) { + s.File = secret.File + } +} + type Service struct { CapabilitiesAdd []string `json:"cap_add,omitempty" yaml:"cap_add,omitempty"` CapabilitiesDrop []string `json:"cap_drop,omitempty" yaml:"cap_drop,omitempty"` @@ -145,20 +238,109 @@ type Service struct { Volumes []string `json:"volumes,omitempty" yaml:"volumes,omitempty"` } +// Equal returns true if the passed equalable is equal +func (s *Service) Equal(equalable Equalable) bool { + service, ok := equalable.(*Service) + if !ok { + return false + } + + switch { + case s == nil && service == nil: + return true + case s != nil && service == nil: + fallthrough + case s == nil && service != nil: + return false + default: + return equalSlice(s.CapabilitiesAdd, service.CapabilitiesAdd) && + equalSlice(s.CapabilitiesDrop, service.CapabilitiesDrop) && + s.Deploy.Equal(service.Deploy) && + equalSlice(s.Environments, service.Environments) && + equalSlice(s.ExtraHosts, service.ExtraHosts) && + s.Image == service.Image && + equalSlice(s.Labels, service.Labels) && + EqualStringMap(s.Networks, service.Networks) && + equalSlice(s.Ports, service.Ports) && + equalSlice(s.Secrets, service.Secrets) && + s.ULimits.Equal(service.ULimits) && + equalSlice(s.Volumes, service.Volumes) + } +} + 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) + if !ok { + return false + } + + switch { + case sd == nil && serviceDeployment == nil: + return true + case sd != nil && serviceDeployment == nil: + fallthrough + case sd == nil && serviceDeployment != nil: + return false + default: + return sd.Resources.Equal(serviceDeployment.Resources) + } +} + type ServiceDeployResources struct { Limits *ServiceDeployResourcesLimits `json:"limits,omitempty" yaml:"limits,omitempty"` Reservations *ServiceDeployResourcesLimits `json:"reservations,omitempty" yaml:"reservations,omitempty"` } +// Equal returns true if the passed equalable is equal +func (sdr *ServiceDeployResources) Equal(equalable Equalable) bool { + serviceDeploymentResources, ok := equalable.(*ServiceDeployResources) + if !ok { + return false + } + + switch { + case sdr == nil && serviceDeploymentResources == nil: + return true + case sdr != nil && serviceDeploymentResources == nil: + fallthrough + case sdr == nil && serviceDeploymentResources != nil: + return false + default: + return sdr.Limits.Equal(serviceDeploymentResources.Limits) && + sdr.Reservations.Equal(serviceDeploymentResources) + } +} + type ServiceDeployResourcesLimits struct { CPUs string `json:"cpus,omitempty" yaml:"cpus,omitempty"` Memory string `json:"memory,omitempty" yaml:"memory,omitempty"` } +// Equal returns true if the passed equalable is equal +func (sdrl *ServiceDeployResourcesLimits) Equal(equalable Equalable) bool { + serviceDeploymentResourcesLimits, ok := equalable.(*ServiceDeployResourcesLimits) + if !ok { + return false + } + + switch { + case sdrl == nil && serviceDeploymentResourcesLimits == nil: + return true + case sdrl != nil && serviceDeploymentResourcesLimits == nil: + fallthrough + case sdrl == nil && serviceDeploymentResourcesLimits != nil: + return false + default: + return sdrl.CPUs == serviceDeploymentResourcesLimits.CPUs && + sdrl.Memory == serviceDeploymentResourcesLimits.Memory + } +} + type ServiceNetwork struct { Aliases []string `json:"aliases,omitempty" yaml:"aliases,omitempty"` } @@ -178,7 +360,7 @@ func (sn *ServiceNetwork) Equal(equalable Equalable) bool { case sn == nil && serviceNetwork != nil: return false default: - //TODO: return false + return equalSlice(sn.Aliases, serviceNetwork.Aliases) } } @@ -254,3 +436,20 @@ func (v *Volume) Equal(equalable Equalable) bool { return v.External == volume.External } } + +func equalSlice[K comparable](sliceA []K, sliceB []K) bool { + equalFunc := func(sliceA []K, sliceB []K) bool { + LOOP: + for i := range sliceA { + for j := range sliceB { + if sliceA[i] == sliceB[j] { + continue LOOP + } + } + return false + } + return true + } + + return equalFunc(sliceA, sliceB) && equalFunc(sliceB, sliceA) +} diff --git a/pki/domain/dockerCompose/equalable.go b/pki/domain/dockerCompose/equalable.go index 45e9a9b..81fec27 100644 --- a/pki/domain/dockerCompose/equalable.go +++ b/pki/domain/dockerCompose/equalable.go @@ -52,3 +52,14 @@ func EqualStringMap[R Equalable](mapA, mapB map[string]R) bool { return equalFunc(mapA, mapB) && equalFunc(mapB, mapA) } + +// ExistsInMap returns true if object of type any exists under the passed name. +func ExistsInMap[T any](m map[string]T, name string) bool { + switch { + case m == nil: + return false + default: + _, present := m[name] + return present + } +}