2023-07-19 19:44:02 +00:00
|
|
|
package dockerCompose
|
|
|
|
|
|
|
|
type Config struct {
|
|
|
|
Networks map[string]*Network `json:"networks,omitempty" yaml:"networks,omitempty"`
|
|
|
|
Secrets map[string]*Secret `json:"secrets,omitempty" yaml:"secrets,omitempty"`
|
|
|
|
Services map[string]*Service `json:"services,omitempty" yaml:"services,omitempty"`
|
|
|
|
Version string `json:"version,omitempty" yaml:"version,omitempty"`
|
|
|
|
Volumes map[string]*Volume `json:"volumes,omitempty" yaml:"volumes,omitempty"`
|
|
|
|
}
|
|
|
|
|
2023-07-20 11:51:15 +00:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-19 19:44:02 +00:00
|
|
|
// ExistsNetwork returns true if a network with the passed named exists.
|
|
|
|
func (c *Config) ExistsNetwork(name string) bool {
|
2023-07-20 11:51:15 +00:00
|
|
|
return ExistsInMap(c.Networks, name)
|
2023-07-19 19:44:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ExistsSecret returns true if a secret with the passed named exists.
|
|
|
|
func (c *Config) ExistsSecret(name string) bool {
|
2023-07-20 11:51:15 +00:00
|
|
|
return ExistsInMap(c.Secrets, name)
|
2023-07-19 19:44:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ExistsService returns true if a service with the passed named exists.
|
|
|
|
func (c *Config) ExistsService(name string) bool {
|
2023-07-20 11:51:15 +00:00
|
|
|
return ExistsInMap(c.Services, name)
|
2023-07-19 19:44:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ExistsVolumes returns true if a volume with the passed named exists.
|
|
|
|
func (c *Config) ExistsVolume(name string) bool {
|
2023-07-20 11:51:15 +00:00
|
|
|
return ExistsInMap(c.Volumes, name)
|
2023-07-19 19:44:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// MergeLastWin merges a config and overwrite already existing properties
|
|
|
|
func (c *Config) MergeLastWin(config *Config) {
|
|
|
|
if c.Version != config.Version {
|
|
|
|
c.Version = config.Version
|
|
|
|
}
|
|
|
|
|
|
|
|
for networkName, network := range c.Networks {
|
|
|
|
if network == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.ExistsNetwork(networkName) {
|
|
|
|
c.Networks[networkName].MergeLastWin(network)
|
|
|
|
} else {
|
|
|
|
c.Networks[networkName] = network
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-20 11:51:15 +00:00
|
|
|
for secretName, secret := range c.Secrets {
|
|
|
|
if secret == nil {
|
|
|
|
continue
|
|
|
|
}
|
2023-07-19 19:44:02 +00:00
|
|
|
|
2023-07-20 11:51:15 +00:00
|
|
|
if c.ExistsSecret(secretName) {
|
|
|
|
c.Secrets[secretName].MergeLastWin(secret)
|
|
|
|
} else {
|
|
|
|
c.Secrets[secretName] = secret
|
|
|
|
}
|
|
|
|
}
|
2023-07-19 19:44:02 +00:00
|
|
|
|
2023-07-20 11:51:15 +00:00
|
|
|
for serviceName, service := range c.Services {
|
|
|
|
if service == nil {
|
|
|
|
continue
|
|
|
|
}
|
2023-07-19 19:44:02 +00:00
|
|
|
|
2023-07-20 11:51:15 +00:00
|
|
|
if c.ExistsService(serviceName) {
|
|
|
|
c.Services[serviceName].MergeLastWin(service)
|
|
|
|
} else {
|
|
|
|
c.Services[serviceName] = service
|
|
|
|
}
|
|
|
|
}
|
2023-07-19 19:44:02 +00:00
|
|
|
|
|
|
|
// for volumeName, volume := range c.Volumes {
|
|
|
|
// if volume == nil {
|
|
|
|
// continue
|
|
|
|
// }
|
|
|
|
|
|
|
|
// if c.ExistsVolume(volumeName) {
|
|
|
|
// c.Volumes[volumeName].MergeLastWin(volume)
|
|
|
|
// } else {
|
|
|
|
// c.Volumes[volumeName] = volume
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewConfig() *Config {
|
|
|
|
return &Config{
|
|
|
|
Services: make(map[string]*Service),
|
|
|
|
Networks: make(map[string]*Network),
|
|
|
|
Secrets: make(map[string]*Secret),
|
|
|
|
Volumes: make(map[string]*Volume),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type Network struct {
|
|
|
|
External bool `json:"external,omitempty" yaml:"external,omitempty"`
|
|
|
|
Driver string `json:"driver,omitempty" yaml:"driver,omitempty"`
|
|
|
|
IPAM *NetworkIPAM `json:"ipam,omitempty" yaml:"ipam,omitempty"`
|
|
|
|
}
|
|
|
|
|
2023-07-20 11:51:15 +00:00
|
|
|
// Equal returns true if the passed equalable is equal
|
|
|
|
func (n *Network) Equal(equalable Equalable) bool {
|
|
|
|
network, ok := equalable.(*Network)
|
|
|
|
if !ok {
|
|
|
|
return false
|
2023-07-19 19:44:02 +00:00
|
|
|
}
|
|
|
|
|
2023-07-20 11:51:15 +00:00
|
|
|
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)
|
2023-07-19 19:44:02 +00:00
|
|
|
}
|
2023-07-20 11:51:15 +00:00
|
|
|
}
|
2023-07-19 19:44:02 +00:00
|
|
|
|
2023-07-20 11:51:15 +00:00
|
|
|
func (n *Network) MergeLastWin(network *Network) {
|
|
|
|
if !n.IPAM.Equal(network.IPAM) {
|
|
|
|
n.IPAM = network.IPAM
|
|
|
|
}
|
2023-07-19 19:44:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type NetworkIPAM struct {
|
|
|
|
Config []*NetworkIPAMConfig `json:"config,omitempty" yaml:"config,omitempty"`
|
|
|
|
}
|
|
|
|
|
2023-07-20 11:51:15 +00:00
|
|
|
// Equal returns true if the passed equalable is equal
|
|
|
|
func (nIPAM *NetworkIPAM) Equal(equalable Equalable) bool {
|
|
|
|
networkIPAM, ok := equalable.(*NetworkIPAM)
|
|
|
|
if !ok {
|
|
|
|
return false
|
2023-07-19 19:44:02 +00:00
|
|
|
}
|
|
|
|
|
2023-07-20 11:51:15 +00:00
|
|
|
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)
|
|
|
|
}
|
2023-07-19 19:44:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type NetworkIPAMConfig struct {
|
|
|
|
Subnet string `json:"subnet,omitempty" yaml:"subnet,omitempty"`
|
|
|
|
}
|
|
|
|
|
2023-07-20 11:51:15 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-19 19:44:02 +00:00
|
|
|
type Secret struct {
|
|
|
|
File string `json:"file,omitempty" yaml:"file,omitempty"`
|
|
|
|
}
|
|
|
|
|
2023-07-20 11:51:15 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-19 19:44:02 +00:00
|
|
|
type Service struct {
|
|
|
|
CapabilitiesAdd []string `json:"cap_add,omitempty" yaml:"cap_add,omitempty"`
|
|
|
|
CapabilitiesDrop []string `json:"cap_drop,omitempty" yaml:"cap_drop,omitempty"`
|
|
|
|
Deploy *ServiceDeploy `json:"deploy,omitempty" yaml:"deploy,omitempty"`
|
|
|
|
Environments []string `json:"environment,omitempty" yaml:"environment,omitempty"`
|
|
|
|
ExtraHosts []string `json:"extra_hosts,omitempty" yaml:"extra_hosts,omitempty"`
|
|
|
|
Image string `json:"image,omitempty" yaml:"image,omitempty"`
|
|
|
|
Labels []string `json:"labels,omitempty" yaml:"labels,omitempty"`
|
|
|
|
Networks map[string]*ServiceNetwork `json:"networks,omitempty" yaml:"networks,omitempty"`
|
|
|
|
Ports []string `json:"ports,omitempty" yaml:"ports,omitempty"`
|
|
|
|
Secrets []string `json:"secrets,omitempty" yaml:"secrets,omitempty"`
|
|
|
|
ULimits *ServiceULimits `json:"ulimits,omitempty" yaml:"ulimits,omitempty"`
|
|
|
|
Volumes []string `json:"volumes,omitempty" yaml:"volumes,omitempty"`
|
|
|
|
}
|
|
|
|
|
2023-07-20 11:51:15 +00:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-19 19:44:02 +00:00
|
|
|
type ServiceDeploy struct {
|
|
|
|
Resources *ServiceDeployResources `json:"resources" yaml:"resources"`
|
|
|
|
}
|
|
|
|
|
2023-07-20 11:51:15 +00:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-19 19:44:02 +00:00
|
|
|
type ServiceDeployResources struct {
|
|
|
|
Limits *ServiceDeployResourcesLimits `json:"limits,omitempty" yaml:"limits,omitempty"`
|
|
|
|
Reservations *ServiceDeployResourcesLimits `json:"reservations,omitempty" yaml:"reservations,omitempty"`
|
|
|
|
}
|
|
|
|
|
2023-07-20 11:51:15 +00:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-19 19:44:02 +00:00
|
|
|
type ServiceDeployResourcesLimits struct {
|
|
|
|
CPUs string `json:"cpus,omitempty" yaml:"cpus,omitempty"`
|
|
|
|
Memory string `json:"memory,omitempty" yaml:"memory,omitempty"`
|
|
|
|
}
|
|
|
|
|
2023-07-20 11:51:15 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-19 19:44:02 +00:00
|
|
|
type ServiceNetwork struct {
|
|
|
|
Aliases []string `json:"aliases,omitempty" yaml:"aliases,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Equal returns true if the passed equalable is equal
|
|
|
|
func (sn *ServiceNetwork) Equal(equalable Equalable) bool {
|
|
|
|
serviceNetwork, ok := equalable.(*ServiceNetwork)
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case sn == nil && serviceNetwork == nil:
|
|
|
|
return true
|
|
|
|
case sn != nil && serviceNetwork == nil:
|
|
|
|
fallthrough
|
|
|
|
case sn == nil && serviceNetwork != nil:
|
|
|
|
return false
|
|
|
|
default:
|
2023-07-20 11:51:15 +00:00
|
|
|
return equalSlice(sn.Aliases, serviceNetwork.Aliases)
|
2023-07-19 19:44:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type ServiceULimits struct {
|
|
|
|
NProc uint `json:"nproc,omitempty" yaml:"nproc,omitempty"`
|
|
|
|
NoFile *ServiceULimitsNoFile `json:"nofile,omitempty" yaml:"nofile,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Equal returns true if the passed equalable is equal
|
|
|
|
func (l *ServiceULimits) Equal(equalable Equalable) bool {
|
|
|
|
serviceULimits, ok := equalable.(*ServiceULimits)
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case l == nil && serviceULimits == nil:
|
|
|
|
return true
|
|
|
|
case l != nil && serviceULimits == nil:
|
|
|
|
fallthrough
|
|
|
|
case l == nil && serviceULimits != nil:
|
|
|
|
return false
|
|
|
|
default:
|
|
|
|
return l.NProc == serviceULimits.NProc &&
|
|
|
|
l.NoFile.Equal(serviceULimits.NoFile)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type ServiceULimitsNoFile struct {
|
|
|
|
Hard uint `json:"hard" yaml:"hard"`
|
|
|
|
Soft uint `json:"soft" yaml:"soft"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Equal returns true if the passed equalable is equal
|
|
|
|
func (nf *ServiceULimitsNoFile) Equal(equalable Equalable) bool {
|
|
|
|
serviceULimitsNoFile, ok := equalable.(*ServiceULimitsNoFile)
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case nf == nil && serviceULimitsNoFile == nil:
|
|
|
|
return true
|
|
|
|
case nf != nil && serviceULimitsNoFile == nil:
|
|
|
|
fallthrough
|
|
|
|
case nf == nil && serviceULimitsNoFile != nil:
|
|
|
|
return false
|
|
|
|
default:
|
|
|
|
return nf.Hard == serviceULimitsNoFile.Hard &&
|
|
|
|
nf.Soft == serviceULimitsNoFile.Soft
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type Volume struct {
|
|
|
|
External bool `json:"external,omitempty" yaml:"external,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Equal returns true if the passed equalable is equal
|
|
|
|
func (v *Volume) Equal(equalable Equalable) bool {
|
|
|
|
volume, ok := equalable.(*Volume)
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case v == nil && volume == nil:
|
|
|
|
return true
|
|
|
|
case v != nil && volume == nil:
|
|
|
|
fallthrough
|
|
|
|
case v == nil && volume != nil:
|
|
|
|
return false
|
|
|
|
default:
|
|
|
|
return v.External == volume.External
|
|
|
|
}
|
|
|
|
}
|
2023-07-20 11:51:15 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|