diff --git a/cmd/root.go b/cmd/root.go index 91f5376..0ceb5d5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,7 +8,7 @@ import ( "git.cryptic.systems/volker.raschek/dcmerge/pkg/domain/dockerCompose" "git.cryptic.systems/volker.raschek/dcmerge/pkg/fetcher" "github.com/spf13/cobra" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" ) func Execute(version string) error { @@ -100,10 +100,12 @@ func run(cmd *cobra.Command, args []string) error { defer f.Close() yamlEncoder := yaml.NewEncoder(f) + yamlEncoder.SetIndent(0) return yamlEncoder.Encode(dockerComposeConfig) default: yamlEncoder := yaml.NewEncoder(os.Stdout) + yamlEncoder.SetIndent(0) return yamlEncoder.Encode(dockerComposeConfig) } diff --git a/go.mod b/go.mod index b315df9..6b53671 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/spf13/cobra v1.9.1 github.com/stretchr/testify v1.10.0 - gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -13,5 +13,4 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.6 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 13a7715..4695b18 100644 --- a/go.sum +++ b/go.sum @@ -14,7 +14,5 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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/pkg/domain/dockerCompose/config.go b/pkg/domain/dockerCompose/config.go index 489489b..946b672 100644 --- a/pkg/domain/dockerCompose/config.go +++ b/pkg/domain/dockerCompose/config.go @@ -4,6 +4,8 @@ import ( "fmt" "regexp" "strings" + + "gopkg.in/yaml.v3" ) const ( @@ -517,19 +519,19 @@ func NewSecret() *Secret { } type Service struct { - CapabilitiesAdd []string `json:"cap_add,omitempty" yaml:"cap_add,omitempty"` - CapabilitiesDrop []string `json:"cap_drop,omitempty" yaml:"cap_drop,omitempty"` - DependsOn []string `json:"depends_on,omitempty" yaml:"depends_on,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 []Port `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"` + CapabilitiesAdd []string `json:"cap_add,omitempty" yaml:"cap_add,omitempty"` + CapabilitiesDrop []string `json:"cap_drop,omitempty" yaml:"cap_drop,omitempty"` + DependsOnContainer *DependsOnContainer `json:"depends_on,omitempty" yaml:"depends_on,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 []Port `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"` } // ExistsEnvironment returns true if the passed name of environment variable is @@ -629,7 +631,7 @@ func (s *Service) Equal(equalable Equalable) bool { default: return equalSlice(s.CapabilitiesAdd, service.CapabilitiesAdd) && equalSlice(s.CapabilitiesDrop, service.CapabilitiesDrop) && - equalSlice(s.DependsOn, service.DependsOn) && + s.DependsOnContainer.Equal(service.DependsOnContainer) && s.Deploy.Equal(service.Deploy) && equalSlice(s.Environments, service.Environments) && equalSlice(s.ExtraHosts, service.ExtraHosts) && @@ -661,7 +663,7 @@ func (s *Service) MergeExistingWin(service *Service) { default: s.mergeExistingWinCapabilitiesAdd(service.CapabilitiesAdd) s.mergeExistingWinCapabilitiesDrop(service.CapabilitiesDrop) - s.mergeExistingWinDependsOn(service.DependsOn) + s.mergeExistingWinDependsOnContainer(service.DependsOnContainer) s.mergeExistingWinDeploy(service.Deploy) s.mergeExistingWinEnvironments(service.Environments) s.mergeExistingWinExtraHosts(service.ExtraHosts) @@ -695,7 +697,7 @@ func (s *Service) MergeLastWin(service *Service) { default: s.mergeLastWinCapabilitiesAdd(service.CapabilitiesAdd) s.mergeLastWinCapabilitiesDrop(service.CapabilitiesDrop) - s.mergeLastWinDependsOn(service.DependsOn) + s.mergeLastWinDependsOnContainer(service.DependsOnContainer) s.mergeLastWinDeploy(service.Deploy) s.mergeLastWinEnvironments(service.Environments) s.mergeLastWinExtraHosts(service.ExtraHosts) @@ -725,10 +727,22 @@ func (s *Service) mergeExistingWinCapabilitiesDrop(capabilitiesDrop []string) { } } -func (s *Service) mergeExistingWinDependsOn(dependsOn []string) { - for _, depOn := range dependsOn { - if !existsInSlice(s.DependsOn, depOn) && len(depOn) > 0 { - s.DependsOn = append(s.DependsOn, depOn) +func (s *Service) mergeExistingWinDependsOnContainer(dependsOnContainer *DependsOnContainer) { + switch { + case s.DependsOnContainer != nil && dependsOnContainer == nil: + fallthrough + case s.DependsOnContainer == nil && dependsOnContainer == nil: + return + case s.DependsOnContainer == nil && dependsOnContainer != nil: + s.DependsOnContainer = dependsOnContainer + default: + for name, depOn := range dependsOnContainer.DependsOn { + if !ExistsInMap(s.DependsOnContainer.DependsOn, name) && depOn != nil { + if s.DependsOnContainer.DependsOn == nil { + s.DependsOnContainer.DependsOn = make(map[string]*ServiceDependsOn) + } + s.DependsOnContainer.DependsOn[name] = depOn + } } } } @@ -933,14 +947,20 @@ func (s *Service) mergeLastWinCapabilitiesDrop(capabilitiesDrop []string) { } } -func (s *Service) mergeLastWinDependsOn(dependsOn []string) { - for _, dep := range dependsOn { - if len(dep) <= 0 { - continue - } - - if !existsInSlice(s.DependsOn, dep) { - s.DependsOn = append(s.DependsOn, dep) +func (s *Service) mergeLastWinDependsOnContainer(dependsOnContainer *DependsOnContainer) { + switch { + case s.DependsOnContainer != nil && dependsOnContainer == nil: + fallthrough + case s.DependsOnContainer == nil && dependsOnContainer == nil: + return + case s.DependsOnContainer == nil && dependsOnContainer != nil: + s.DependsOnContainer = dependsOnContainer + default: + for name, depOn := range dependsOnContainer.DependsOn { + if s.DependsOnContainer.DependsOn == nil { + s.DependsOnContainer.DependsOn = make(map[string]*ServiceDependsOn) + } + s.DependsOnContainer.DependsOn[name] = depOn } } } @@ -1222,6 +1242,66 @@ func (s *Service) SetVolume(src string, dest string, perm string) { } } +const ServiceDependsOnConditionServiceStarted string = "service_started" + +// DependsOnContainer is a wrapper to handle different YAML type formats of DependsOn. +type DependsOnContainer struct { + Slice []string + DependsOn map[string]*ServiceDependsOn +} + +// Equal returns true if the passed equalable is equal +func (sdoc *DependsOnContainer) Equal(equalable Equalable) bool { + serviceDependsOnContainer, ok := equalable.(*DependsOnContainer) + if !ok { + return false + } + + switch { + case sdoc == nil && serviceDependsOnContainer == nil: + return true + case sdoc != nil && serviceDependsOnContainer == nil: + fallthrough + case sdoc == nil && serviceDependsOnContainer != nil: + return false + default: + return equalSlice(sdoc.Slice, serviceDependsOnContainer.Slice) && + EqualStringMap(sdoc.DependsOn, serviceDependsOnContainer.DependsOn) + } +} + +// MarshalYAML implements the MarshalYAML interface to customize the behavior when being marshaled into a YAML document. +func (sdoc *DependsOnContainer) MarshalYAML() (interface{}, error) { + return sdoc.DependsOn, nil +} + +// UnmarshalYAML implements the UnmarshalYAML interface to customize the behavior when being unmarshaled into a YAML +// document. +func (sdoc *DependsOnContainer) UnmarshalYAML(value *yaml.Node) error { + if sdoc.DependsOn == nil { + sdoc.DependsOn = make(map[string]*ServiceDependsOn) + } + + if sdoc.Slice == nil { + sdoc.Slice = make([]string, 0) + } + + if err := value.Decode(&sdoc.Slice); err == nil { + for _, s := range sdoc.Slice { + sdoc.DependsOn[s] = &ServiceDependsOn{ + Condition: ServiceDependsOnConditionServiceStarted, + } + } + return nil + } + + if err := value.Decode(sdoc.DependsOn); err != nil { + return err + } + + return nil +} + // NewService returns an empty initialized Service. func NewService() *Service { return &Service{ @@ -1239,6 +1319,31 @@ func NewService() *Service { } } +type ServiceDependsOn struct { + Condition string `yaml:"condition,omitempty"` + Restart string `yaml:"restart,omitempty"` +} + +// Equal returns true if the passed equalable is equal +func (sdo *ServiceDependsOn) Equal(equalable Equalable) bool { + serviceDependsOn, ok := equalable.(*ServiceDependsOn) + if !ok { + return false + } + + switch { + case sdo == nil && serviceDependsOn == nil: + return true + case sdo != nil && serviceDependsOn == nil: + fallthrough + case sdo == nil && serviceDependsOn != nil: + return false + default: + return sdo.Condition == serviceDependsOn.Condition && + sdo.Restart == serviceDependsOn.Restart + } +} + type ServiceDeploy struct { Resources *ServiceDeployResources `json:"resources" yaml:"resources"` } diff --git a/pkg/domain/dockerCompose/config_test.go b/pkg/domain/dockerCompose/config_test.go index a911a66..96fa6ad 100644 --- a/pkg/domain/dockerCompose/config_test.go +++ b/pkg/domain/dockerCompose/config_test.go @@ -223,34 +223,34 @@ func TestService_Equal(t *testing.T) { }, { equalableA: &dockerCompose.Service{ - CapabilitiesAdd: []string{}, - CapabilitiesDrop: []string{}, - DependsOn: []string{}, - Deploy: nil, - Environments: []string{}, - ExtraHosts: []string{}, - Image: "", - Labels: []string{}, - Networks: map[string]*dockerCompose.ServiceNetwork{}, - Ports: []dockerCompose.Port{}, - Secrets: []string{}, - ULimits: nil, - Volumes: []string{}, + CapabilitiesAdd: []string{}, + CapabilitiesDrop: []string{}, + DependsOnContainer: &dockerCompose.DependsOnContainer{}, + Deploy: nil, + Environments: []string{}, + ExtraHosts: []string{}, + Image: "", + Labels: []string{}, + Networks: map[string]*dockerCompose.ServiceNetwork{}, + Ports: []dockerCompose.Port{}, + Secrets: []string{}, + ULimits: nil, + Volumes: []string{}, }, equalableB: &dockerCompose.Service{ - CapabilitiesAdd: []string{}, - CapabilitiesDrop: []string{}, - DependsOn: []string{}, - Deploy: nil, - Environments: []string{}, - ExtraHosts: []string{}, - Image: "", - Labels: []string{}, - Networks: map[string]*dockerCompose.ServiceNetwork{}, - Ports: []dockerCompose.Port{}, - Secrets: []string{}, - ULimits: nil, - Volumes: []string{}, + CapabilitiesAdd: []string{}, + CapabilitiesDrop: []string{}, + DependsOnContainer: &dockerCompose.DependsOnContainer{}, + Deploy: nil, + Environments: []string{}, + ExtraHosts: []string{}, + Image: "", + Labels: []string{}, + Networks: map[string]*dockerCompose.ServiceNetwork{}, + Ports: []dockerCompose.Port{}, + Secrets: []string{}, + ULimits: nil, + Volumes: []string{}, }, expectedResult: true, }, @@ -292,19 +292,37 @@ func TestService_Equal(t *testing.T) { }, { equalableA: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, equalableB: &dockerCompose.Service{ - DependsOn: []string{}, + DependsOnContainer: &dockerCompose.DependsOnContainer{}, }, expectedResult: false, }, { equalableA: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, equalableB: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{}, + }, + expectedResult: false, + }, + { + equalableA: &dockerCompose.Service{ + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, + }, + equalableB: &dockerCompose.Service{ + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{}}, + }, + expectedResult: false, + }, + { + equalableA: &dockerCompose.Service{ + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, + }, + equalableB: &dockerCompose.Service{ + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, expectedResult: true, }, @@ -598,46 +616,46 @@ func TestService_MergeExistingWin(t *testing.T) { // DependsOn { serviceDeploymentA: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, serviceDeploymentB: &dockerCompose.Service{ - DependsOn: []string{}, + DependsOnContainer: &dockerCompose.DependsOnContainer{}, }, expectedService: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, }, { serviceDeploymentA: &dockerCompose.Service{ - DependsOn: []string{}, + DependsOnContainer: &dockerCompose.DependsOnContainer{}, }, serviceDeploymentB: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, expectedService: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, }, { serviceDeploymentA: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, serviceDeploymentB: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, expectedService: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, }, { serviceDeploymentA: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, serviceDeploymentB: &dockerCompose.Service{ - DependsOn: []string{""}, + DependsOnContainer: nil, }, expectedService: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, }, @@ -1582,46 +1600,46 @@ func TestService_MergeLastWin(t *testing.T) { // DependsOn { serviceDeploymentA: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, serviceDeploymentB: &dockerCompose.Service{ - DependsOn: []string{}, + DependsOnContainer: &dockerCompose.DependsOnContainer{}, }, expectedService: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, }, { serviceDeploymentA: &dockerCompose.Service{ - DependsOn: []string{}, + DependsOnContainer: &dockerCompose.DependsOnContainer{}, }, serviceDeploymentB: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, expectedService: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, }, { serviceDeploymentA: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, serviceDeploymentB: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, expectedService: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, }, { serviceDeploymentA: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, serviceDeploymentB: &dockerCompose.Service{ - DependsOn: []string{""}, + DependsOnContainer: nil, }, expectedService: &dockerCompose.Service{ - DependsOn: []string{"app"}, + DependsOnContainer: &dockerCompose.DependsOnContainer{DependsOn: map[string]*dockerCompose.ServiceDependsOn{"app": {Condition: "service_started"}}}, }, }, diff --git a/pkg/fetcher/fetcher.go b/pkg/fetcher/fetcher.go index 6ba7e2f..909861a 100644 --- a/pkg/fetcher/fetcher.go +++ b/pkg/fetcher/fetcher.go @@ -8,7 +8,7 @@ import ( "os" "git.cryptic.systems/volker.raschek/dcmerge/pkg/domain/dockerCompose" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" ) func Fetch(urls ...string) ([]*dockerCompose.Config, error) { diff --git a/renovate.json b/renovate.json index 3e8a4b0..382e3aa 100644 --- a/renovate.json +++ b/renovate.json @@ -30,8 +30,7 @@ "matchManagers": [ "gomod" ], "matchPackageNames": [ "gopkg.in/yaml.v2" - ], - "matchUpdateTypes": [ "major" ] + ] } ], "postUpdateOptions": [