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"` } // 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 ExistsInMap(c.Networks, name) } // ExistsSecret returns true if a secret with the passed named exists. func (c *Config) ExistsSecret(name string) bool { return ExistsInMap(c.Secrets, name) } // ExistsService returns true if a service with the passed named exists. func (c *Config) ExistsService(name string) bool { return ExistsInMap(c.Services, name) } // ExistsVolumes returns true if a volume with the passed named exists. func (c *Config) ExistsVolume(name string) bool { return ExistsInMap(c.Volumes, name) } // MergeLastWin merges a config and overwrite already existing properties func (c *Config) MergeLastWin(config *Config) { switch { case c == nil && config == nil: fallthrough case c != nil && config == nil: return // 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 } if c.ExistsNetwork(networkName) { c.Networks[networkName].MergeLastWin(network) } else { c.Networks[networkName] = network } } } func (c *Config) mergeLastWinSecrets(secrets map[string]*Secret) { for secretName, secret := range secrets { if secret == nil { continue } if c.ExistsNetwork(secretName) { c.Secrets[secretName].MergeLastWin(secret) } else { c.Secrets[secretName] = secret } } } func (c *Config) mergeLastWinServices(services map[string]*Service) { for serviceName, service := range services { if service == nil { continue } if c.ExistsService(serviceName) { c.Services[serviceName].MergeLastWin(service) } else { c.Services[serviceName] = service } } } func (c *Config) mergeLastWinVolumes(volumes map[string]*Volume) { for volumeName, volume := range volumes { if volume == nil { continue } if c.ExistsNetwork(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"` } // 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) { 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 { Configs []*NetworkIPAMConfig `json:"config,omitempty" yaml:"config,omitempty"` } // 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.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), } } 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 } } func NewNetworkIPAMConfig() *NetworkIPAMConfig { return &NetworkIPAMConfig{} } 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 } } // 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"` 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"` } // 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) } } // 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 { serviceDeploy, ok := equalable.(*ServiceDeploy) if !ok { return false } switch { case sd == nil && serviceDeploy == nil: return true case sd != nil && serviceDeploy == nil: fallthrough case sd == nil && serviceDeploy != nil: return false default: 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), } } 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 { serviceDeployResources, ok := equalable.(*ServiceDeployResources) if !ok { return false } switch { case sdr == nil && serviceDeployResources == nil: return true case sdr != nil && serviceDeployResources == nil: fallthrough case sdr == nil && serviceDeployResources != nil: return false default: return sdr.Limits.Equal(serviceDeployResources.Limits) && sdr.Reservations.Equal(serviceDeployResources.Reservations) } } // 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), } } 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 { serviceDeployResourcesLimits, ok := equalable.(*ServiceDeployResourcesLimits) if !ok { return false } switch { case sdrl == nil && serviceDeployResourcesLimits == nil: return true case sdrl != nil && serviceDeployResourcesLimits == nil: fallthrough case sdrl == nil && serviceDeployResourcesLimits != nil: return false default: 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"` } // 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: return equalSlice(sn.Aliases, serviceNetwork.Aliases) } } // 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"` } // 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) } } // 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"` } // 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 } } // 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"` } // 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 } } 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: 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) }