From 6d50c6b0605ea54ee61516a7e1b3c8993864b0d9 Mon Sep 17 00:00:00 2001 From: Markus Pesch Date: Wed, 19 Jul 2023 21:44:02 +0200 Subject: [PATCH] Initial Commit --- cmd/root.go | 0 go.mod | 3 + pki/domain/dockerCompose/config.go | 256 ++++++++++++++++++++++++++ pki/domain/dockerCompose/equalable.go | 54 ++++++ 4 files changed, 313 insertions(+) create mode 100644 cmd/root.go create mode 100644 go.mod create mode 100644 pki/domain/dockerCompose/config.go create mode 100644 pki/domain/dockerCompose/equalable.go diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..e69de29 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b999b4e --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.cryptic.systems/volker.raschek/dcmerge + +go 1.20 diff --git a/pki/domain/dockerCompose/config.go b/pki/domain/dockerCompose/config.go new file mode 100644 index 0000000..a533654 --- /dev/null +++ b/pki/domain/dockerCompose/config.go @@ -0,0 +1,256 @@ +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"` +} + +// ExistsNetwork returns true if a network with the passed named exists. +func (c *Config) ExistsNetwork(name string) bool { + return existsKeyInMap(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) +} + +// ExistsService returns true if a service with the passed named exists. +func (c *Config) ExistsService(name string) bool { + return existsKeyInMap(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) +} + +// 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 + } + } + + // for secretName, secret := range c.Secrets { + // if secret == nil { + // continue + // } + + // if c.ExistsSecret(secretName) { + // c.Secrets[secretName].MergeLastWin(secret) + // } else { + // c.Secrets[secretName] = secret + // } + // } + + // for serviceName, service := range c.Services { + // if service == nil { + // continue + // } + + // if c.ExistsService(serviceName) { + // c.Services[serviceName].MergeLastWin(service) + // } else { + // c.Services[serviceName] = service + // } + // } + + // 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"` +} + +// MergeLastWin merges a network and overwrite already existing properties +func (n *Network) MergeLastWin(network *Network) { + if n.External != network.External { + n.External = network.External + } + + 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 + } + +} + +type NetworkIPAMConfig struct { + Subnet string `json:"subnet,omitempty" yaml:"subnet,omitempty"` +} + +type Secret struct { + File string `json:"file,omitempty" yaml:"file,omitempty"` +} + +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"` +} + +type ServiceDeploy struct { + Resources *ServiceDeployResources `json:"resources" yaml:"resources"` +} + +type ServiceDeployResources struct { + Limits *ServiceDeployResourcesLimits `json:"limits,omitempty" yaml:"limits,omitempty"` + Reservations *ServiceDeployResourcesLimits `json:"reservations,omitempty" yaml:"reservations,omitempty"` +} + +type ServiceDeployResourcesLimits struct { + CPUs string `json:"cpus,omitempty" yaml:"cpus,omitempty"` + Memory string `json:"memory,omitempty" yaml:"memory,omitempty"` +} + +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: + //TODO: return false + } +} + +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 + } +} diff --git a/pki/domain/dockerCompose/equalable.go b/pki/domain/dockerCompose/equalable.go new file mode 100644 index 0000000..45e9a9b --- /dev/null +++ b/pki/domain/dockerCompose/equalable.go @@ -0,0 +1,54 @@ +package dockerCompose + +type Equalable interface { + Equal(equalable Equalable) bool +} + +// Contains returns true when sliceA is in sliceB. +func Contains[R Equalable](sliceA, sliceB []R) bool { + switch { + case sliceA == nil && sliceB == nil: + return true + case sliceA != nil && sliceB == nil: + return false + case sliceA == nil && sliceB != nil: + return false + default: + LOOP: + for i := range sliceA { + for j := range sliceB { + if sliceA[i].Equal(sliceB[j]) { + continue LOOP + } + } + return false + } + return true + } +} + +// Equal returns true when sliceA and sliceB are equal. +func Equal[R Equalable](sliceA, sliceB []R) bool { + return Contains(sliceA, sliceB) && + Contains(sliceB, sliceA) && + len(sliceA) == len(sliceB) +} + +// Equal returns true when booth string maps of Equalable are equal. +func EqualStringMap[R Equalable](mapA, mapB map[string]R) bool { + equalFunc := func(mapA, mapB map[string]R) bool { + LOOP: + for keyA, valueA := range mapA { + for keyB, valueB := range mapB { + if keyA == keyB && + valueA.Equal(valueB) { + continue LOOP + } + } + return false + } + return true + } + + return equalFunc(mapA, mapB) && equalFunc(mapB, mapA) +}