From 3c56ae6e5ef62b74bb9c8dec16101a991167d23c Mon Sep 17 00:00:00 2001 From: Markus Pesch Date: Tue, 4 Mar 2025 17:42:07 +0100 Subject: [PATCH] fix(docker-compose): extend YAML marshaler of service.dependsOn The current implementation of the method DependsOnContainer.MarshalYAML() transform the short syntax into the long syntax. More about booth version types of depends_on is described here: - https://docs.docker.com/reference/compose-file/services/#short-syntax-1 - https://docs.docker.com/reference/compose-file/services/#long-syntax-1 Other applications are not compatible with the long syntax. For this reason the MarshalYAML method has been adapted to take care of the specific syntax. As documented of the long syntax, `depends_on..condition: service_started` is the same as `depends_on: [ 'dependency' ]`, the long syntax will be shortened when no other condition type of a dependency is specified. --- pkg/domain/dockerCompose/config.go | 38 ++++++++- pkg/domain/dockerCompose/config_test.go | 78 +++++++++++++++++++ .../merge/testcase000/docker-compose-000.yml | 6 ++ .../merge/testcase000/docker-compose-001.yml | 3 + .../merge/testcase000/expectedResult.yml | 7 ++ .../merge/testcase001/docker-compose-000.yml | 5 ++ .../merge/testcase001/docker-compose-001.yml | 3 + .../merge/testcase001/expectedResult.yml | 7 ++ .../merge/testcase002/docker-compose-000.yml | 6 ++ .../merge/testcase002/docker-compose-001.yml | 3 + .../merge/testcase002/expectedResult.yml | 8 ++ .../test/assets/mergeExistingWin/.gitkeep | 0 .../test/assets/mergeLastWin/.gitkeep | 0 13 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 pkg/domain/dockerCompose/test/assets/merge/testcase000/docker-compose-000.yml create mode 100644 pkg/domain/dockerCompose/test/assets/merge/testcase000/docker-compose-001.yml create mode 100644 pkg/domain/dockerCompose/test/assets/merge/testcase000/expectedResult.yml create mode 100644 pkg/domain/dockerCompose/test/assets/merge/testcase001/docker-compose-000.yml create mode 100644 pkg/domain/dockerCompose/test/assets/merge/testcase001/docker-compose-001.yml create mode 100644 pkg/domain/dockerCompose/test/assets/merge/testcase001/expectedResult.yml create mode 100644 pkg/domain/dockerCompose/test/assets/merge/testcase002/docker-compose-000.yml create mode 100644 pkg/domain/dockerCompose/test/assets/merge/testcase002/docker-compose-001.yml create mode 100644 pkg/domain/dockerCompose/test/assets/merge/testcase002/expectedResult.yml create mode 100644 pkg/domain/dockerCompose/test/assets/mergeExistingWin/.gitkeep create mode 100644 pkg/domain/dockerCompose/test/assets/mergeLastWin/.gitkeep diff --git a/pkg/domain/dockerCompose/config.go b/pkg/domain/dockerCompose/config.go index 946b672..706942f 100644 --- a/pkg/domain/dockerCompose/config.go +++ b/pkg/domain/dockerCompose/config.go @@ -71,24 +71,36 @@ func (c *Config) ExistsVolume(name string) bool { func (c *Config) Merge(config *Config) { for name, network := range config.Networks { if !c.ExistsNetwork(name) { + if c.Networks == nil { + c.Networks = make(map[string]*Network) + } c.Networks[name] = network } } for name, secret := range config.Secrets { if !c.ExistsSecret(name) { + if c.Secrets == nil { + c.Secrets = make(map[string]*Secret) + } c.Secrets[name] = secret } } for name, service := range config.Services { if !c.ExistsService(name) { + if c.Services == nil { + c.Services = make(map[string]*Service) + } c.Services[name] = service } } for name, volume := range config.Volumes { if !c.ExistsVolume(name) { + if c.Volumes == nil { + c.Volumes = make(map[string]*Volume) + } c.Volumes[name] = volume } } @@ -1242,7 +1254,11 @@ func (s *Service) SetVolume(src string, dest string, perm string) { } } -const ServiceDependsOnConditionServiceStarted string = "service_started" +const ( + ServiceDependsOnConditionServiceCompletedSuccessfully string = "service_completed_successfully" + ServiceDependsOnConditionServiceHealthy string = "service_healthy" + ServiceDependsOnConditionServiceStarted string = "service_started" +) // DependsOnContainer is a wrapper to handle different YAML type formats of DependsOn. type DependsOnContainer struct { @@ -1272,7 +1288,25 @@ func (sdoc *DependsOnContainer) Equal(equalable Equalable) bool { // 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 + var foundAnotherCondition bool = false + var dependencyNames []string + + for dependencyName, dependencyDefinition := range sdoc.DependsOn { + if dependencyDefinition.Condition == ServiceDependsOnConditionServiceStarted { + dependencyNames = append(dependencyNames, dependencyName) + continue + } + foundAnotherCondition = true + } + + switch { + case foundAnotherCondition: + return sdoc.DependsOn, nil + case !foundAnotherCondition && len(dependencyNames) > 0: + return dependencyNames, nil + default: + return nil, nil + } } // UnmarshalYAML implements the UnmarshalYAML interface to customize the behavior when being unmarshaled into a YAML diff --git a/pkg/domain/dockerCompose/config_test.go b/pkg/domain/dockerCompose/config_test.go index d949f3b..3f0e210 100644 --- a/pkg/domain/dockerCompose/config_test.go +++ b/pkg/domain/dockerCompose/config_test.go @@ -1,12 +1,90 @@ package dockerCompose_test import ( + "bytes" + "embed" + "strings" "testing" "git.cryptic.systems/volker.raschek/dcmerge/pkg/domain/dockerCompose" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) +//go:embed test/assets/merge +var testAssetsMerge embed.FS + +func TestConfig_Merge(t *testing.T) { + require := require.New(t) + + testAssetPath := "test/assets/merge" + + testAssetMergeDirEntries, err := testAssetsMerge.ReadDir(testAssetPath) + require.NoError(err) + + // iterate over testcase directories + for i, mergeDirEntry := range testAssetMergeDirEntries { + if !mergeDirEntry.IsDir() { + continue + } + + // iterate over files in testcase directories + testCaseAssetPath := testAssetPath + "/" + mergeDirEntry.Name() + testCaseDirEntries, err := testAssetsMerge.ReadDir(testCaseAssetPath) + require.NoError(err) + + expectedDockerComposeConfig := &dockerCompose.Config{} + dockerComposeConfigs := []*dockerCompose.Config{} + for _, testCaseDirEntry := range testCaseDirEntries { + if testCaseDirEntry.IsDir() { + continue + } + + dockerComposeConfigFile := testAssetPath + "/" + mergeDirEntry.Name() + "/" + testCaseDirEntry.Name() + b, err := testAssetsMerge.ReadFile(dockerComposeConfigFile) + require.NoError(err) + yamlDecoder := yaml.NewDecoder(bytes.NewReader(b)) + + switch { + case strings.HasPrefix(testCaseDirEntry.Name(), "expectedResult"): + err = yamlDecoder.Decode(expectedDockerComposeConfig) + require.NoError(err) + case strings.HasSuffix(testCaseDirEntry.Name(), ".yml") || strings.HasSuffix(testCaseDirEntry.Name(), ".yaml"): + dockerComposeConfig := &dockerCompose.Config{} + err = yamlDecoder.Decode(dockerComposeConfig) + require.NoError(err) + dockerComposeConfigs = append(dockerComposeConfigs, dockerComposeConfig) + } + } + + actualDockerComposeConfig := &dockerCompose.Config{} + for _, dockerComposeConfig := range dockerComposeConfigs { + actualDockerComposeConfig.Merge(dockerComposeConfig) + } + + expectedBytes := make([]byte, 0) + expectedBytesBuffer := bytes.NewBuffer(expectedBytes) + yamlEncoder := yaml.NewEncoder(expectedBytesBuffer) + err = yamlEncoder.Encode(expectedDockerComposeConfig) + require.NoError(err) + + err = yamlEncoder.Close() + require.NoError(err) + + actualBytes := make([]byte, 0) + actualBytesBuffer := bytes.NewBuffer(actualBytes) + yamlEncoder = yaml.NewEncoder(actualBytesBuffer) + err = yamlEncoder.Encode(actualDockerComposeConfig) + require.NoError(err) + + err = yamlEncoder.Close() + require.NoError(err) + + require.Equal(expectedBytesBuffer.String(), actualBytesBuffer.String(), "TestCase %v", i) + } + +} + func TestNetwork_Equal(t *testing.T) { require := require.New(t) diff --git a/pkg/domain/dockerCompose/test/assets/merge/testcase000/docker-compose-000.yml b/pkg/domain/dockerCompose/test/assets/merge/testcase000/docker-compose-000.yml new file mode 100644 index 0000000..ddd6508 --- /dev/null +++ b/pkg/domain/dockerCompose/test/assets/merge/testcase000/docker-compose-000.yml @@ -0,0 +1,6 @@ +services: + frontend: + depends_on: + backend: + condition: service_started + image: library/frontend:latest \ No newline at end of file diff --git a/pkg/domain/dockerCompose/test/assets/merge/testcase000/docker-compose-001.yml b/pkg/domain/dockerCompose/test/assets/merge/testcase000/docker-compose-001.yml new file mode 100644 index 0000000..eb2f6e0 --- /dev/null +++ b/pkg/domain/dockerCompose/test/assets/merge/testcase000/docker-compose-001.yml @@ -0,0 +1,3 @@ +services: + backend: + image: library/backend:latest \ No newline at end of file diff --git a/pkg/domain/dockerCompose/test/assets/merge/testcase000/expectedResult.yml b/pkg/domain/dockerCompose/test/assets/merge/testcase000/expectedResult.yml new file mode 100644 index 0000000..24ae550 --- /dev/null +++ b/pkg/domain/dockerCompose/test/assets/merge/testcase000/expectedResult.yml @@ -0,0 +1,7 @@ +services: + backend: + image: library/backend:latest + frontend: + depends_on: + - backend + image: library/frontend:latest diff --git a/pkg/domain/dockerCompose/test/assets/merge/testcase001/docker-compose-000.yml b/pkg/domain/dockerCompose/test/assets/merge/testcase001/docker-compose-000.yml new file mode 100644 index 0000000..b21e0bd --- /dev/null +++ b/pkg/domain/dockerCompose/test/assets/merge/testcase001/docker-compose-000.yml @@ -0,0 +1,5 @@ +services: + frontend: + depends_on: + - backend + image: library/frontend:latest \ No newline at end of file diff --git a/pkg/domain/dockerCompose/test/assets/merge/testcase001/docker-compose-001.yml b/pkg/domain/dockerCompose/test/assets/merge/testcase001/docker-compose-001.yml new file mode 100644 index 0000000..eb2f6e0 --- /dev/null +++ b/pkg/domain/dockerCompose/test/assets/merge/testcase001/docker-compose-001.yml @@ -0,0 +1,3 @@ +services: + backend: + image: library/backend:latest \ No newline at end of file diff --git a/pkg/domain/dockerCompose/test/assets/merge/testcase001/expectedResult.yml b/pkg/domain/dockerCompose/test/assets/merge/testcase001/expectedResult.yml new file mode 100644 index 0000000..24ae550 --- /dev/null +++ b/pkg/domain/dockerCompose/test/assets/merge/testcase001/expectedResult.yml @@ -0,0 +1,7 @@ +services: + backend: + image: library/backend:latest + frontend: + depends_on: + - backend + image: library/frontend:latest diff --git a/pkg/domain/dockerCompose/test/assets/merge/testcase002/docker-compose-000.yml b/pkg/domain/dockerCompose/test/assets/merge/testcase002/docker-compose-000.yml new file mode 100644 index 0000000..837e5d2 --- /dev/null +++ b/pkg/domain/dockerCompose/test/assets/merge/testcase002/docker-compose-000.yml @@ -0,0 +1,6 @@ +services: + frontend: + depends_on: + backend: + condition: service_completed_successfully + image: library/frontend:latest \ No newline at end of file diff --git a/pkg/domain/dockerCompose/test/assets/merge/testcase002/docker-compose-001.yml b/pkg/domain/dockerCompose/test/assets/merge/testcase002/docker-compose-001.yml new file mode 100644 index 0000000..eb2f6e0 --- /dev/null +++ b/pkg/domain/dockerCompose/test/assets/merge/testcase002/docker-compose-001.yml @@ -0,0 +1,3 @@ +services: + backend: + image: library/backend:latest \ No newline at end of file diff --git a/pkg/domain/dockerCompose/test/assets/merge/testcase002/expectedResult.yml b/pkg/domain/dockerCompose/test/assets/merge/testcase002/expectedResult.yml new file mode 100644 index 0000000..72499ac --- /dev/null +++ b/pkg/domain/dockerCompose/test/assets/merge/testcase002/expectedResult.yml @@ -0,0 +1,8 @@ +services: + backend: + image: library/backend:latest + frontend: + depends_on: + backend: + condition: service_completed_successfully + image: library/frontend:latest diff --git a/pkg/domain/dockerCompose/test/assets/mergeExistingWin/.gitkeep b/pkg/domain/dockerCompose/test/assets/mergeExistingWin/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/pkg/domain/dockerCompose/test/assets/mergeLastWin/.gitkeep b/pkg/domain/dockerCompose/test/assets/mergeLastWin/.gitkeep new file mode 100644 index 0000000..e69de29