From aa04417208f680f25bae78ccff73c8c585fd3bba Mon Sep 17 00:00:00 2001 From: Guillaume Lours Date: Fri, 29 Jul 2022 14:59:43 +0200 Subject: [PATCH 1/2] use '-' as default separator and support of compatibility mode Signed-off-by: Guillaume Lours --- cli/options.go | 12 +++++- cli/options_test.go | 26 ++++++++++++ .../compose-with-network-and-volume.yaml | 11 +++++ loader/loader.go | 10 ++++- loader/normalize.go | 14 +++---- loader/normalize_test.go | 42 ++++++++++++++----- 6 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 cli/testdata/simple/compose-with-network-and-volume.yaml diff --git a/cli/options.go b/cli/options.go index e457b434..35e145d1 100644 --- a/cli/options.go +++ b/cli/options.go @@ -350,7 +350,8 @@ func ProjectFromOptions(options *ProjectOptions) (*types.Project, error) { options.loadOptions = append(options.loadOptions, withNamePrecedenceLoad(absWorkingDir, options), - withConvertWindowsPaths(options)) + withConvertWindowsPaths(options), + withSeparator(options)) project, err := loader.Load(types.ConfigDetails{ ConfigFiles: configs, @@ -384,6 +385,15 @@ func withConvertWindowsPaths(options *ProjectOptions) func(*loader.Options) { } } +// withSeparator defines loader.Options separator used to define resource names +func withSeparator(options *ProjectOptions) func(*loader.Options) { + return func(o *loader.Options) { + if utils.StringToBool(options.Environment["COMPOSE_COMPATIBILITY"]) { + o.Separator = loader.CompatibilitySeparator + } + } +} + // getConfigPathsFromOptions retrieves the config files for project based on project options func getConfigPathsFromOptions(options *ProjectOptions) ([]string, error) { if len(options.ConfigPaths) != 0 { diff --git a/cli/options_test.go b/cli/options_test.go index 3836177f..d8a0bac5 100644 --- a/cli/options_test.go +++ b/cli/options_test.go @@ -237,3 +237,29 @@ func TestEnvMap(t *testing.T) { m = utils.GetAsEqualsMap(l) assert.Equal(t, m["foo"], "bar") } + +func TestWithSeparator(t *testing.T) { + t.Run("With default separator", func(t *testing.T) { + opts, err := NewProjectOptions([]string{ + "testdata/simple/compose-with-network-and-volume.yaml", + }, WithName("my-project")) + assert.NilError(t, err) + p, err := ProjectFromOptions(opts) + assert.NilError(t, err) + assert.Equal(t, p.Networks["test-network"].Name, "my-project-test-network") + + }) + + t.Run("With compatibility separator", func(t *testing.T) { + os.Setenv("COMPOSE_COMPATIBILITY", "true") //nolint:errcheck + defer os.Unsetenv("COMPOSE_COMPATIBILITY") //nolint:errcheck + opts, err := NewProjectOptions([]string{ + "testdata/simple/compose-with-network-and-volume.yaml", + }, WithName("my-project"), WithOsEnv) + assert.NilError(t, err) + p, err := ProjectFromOptions(opts) + assert.NilError(t, err) + assert.Equal(t, p.Networks["test-network"].Name, "my-project_test-network") + + }) +} diff --git a/cli/testdata/simple/compose-with-network-and-volume.yaml b/cli/testdata/simple/compose-with-network-and-volume.yaml new file mode 100644 index 00000000..893fb7b3 --- /dev/null +++ b/cli/testdata/simple/compose-with-network-and-volume.yaml @@ -0,0 +1,11 @@ +services: + simple: + image: nginx + networks: + - test-network + volumes: + - test-volume +networks: + test-network: +volumes: + test-volume: diff --git a/loader/loader.go b/loader/loader.go index acf11a95..6b7930ad 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -43,6 +43,11 @@ import ( "gopkg.in/yaml.v2" ) +const ( + DefaultSeparator = "-" + CompatibilitySeparator = "_" +) + // Options supported by Load type Options struct { // Skip schema validation @@ -67,6 +72,8 @@ type Options struct { projectName string // Indicates when the projectName was imperatively set or guessed from path projectNameImperativelySet bool + // Set separator used for naming resources + Separator string } func (o *Options) SetProjectName(name string, imperativelySet bool) { @@ -155,6 +162,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types. LookupValue: configDetails.LookupEnv, TypeCastMapping: interpolateTypeCastMapping, }, + Separator: DefaultSeparator, } for _, op := range options { @@ -223,7 +231,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types. } if !opts.SkipNormalization { - err = normalize(project, opts.ResolvePaths) + err = normalize(project, opts.ResolvePaths, opts.Separator) if err != nil { return nil, err } diff --git a/loader/normalize.go b/loader/normalize.go index 4b98d624..44498b4d 100644 --- a/loader/normalize.go +++ b/loader/normalize.go @@ -28,7 +28,7 @@ import ( ) // normalize compose project by moving deprecated attributes to their canonical position and injecting implicit defaults -func normalize(project *types.Project, resolvePaths bool) error { +func normalize(project *types.Project, resolvePaths bool, separator string) error { absWorkingDir, err := filepath.Abs(project.WorkingDir) if err != nil { return err @@ -110,7 +110,7 @@ func normalize(project *types.Project, resolvePaths bool) error { project.Services[i] = s } - setNameFromKey(project) + setNameFromKey(project, separator) return nil } @@ -143,31 +143,31 @@ func absComposeFiles(composeFiles []string) ([]string, error) { } // Resources with no explicit name are actually named by their key in map -func setNameFromKey(project *types.Project) { +func setNameFromKey(project *types.Project, separator string) { for i, n := range project.Networks { if n.Name == "" { - n.Name = fmt.Sprintf("%s_%s", project.Name, i) + n.Name = fmt.Sprintf("%s%s%s", project.Name, separator, i) project.Networks[i] = n } } for i, v := range project.Volumes { if v.Name == "" { - v.Name = fmt.Sprintf("%s_%s", project.Name, i) + v.Name = fmt.Sprintf("%s%s%s", project.Name, separator, i) project.Volumes[i] = v } } for i, c := range project.Configs { if c.Name == "" { - c.Name = fmt.Sprintf("%s_%s", project.Name, i) + c.Name = fmt.Sprintf("%s%s%s", project.Name, separator, i) project.Configs[i] = c } } for i, s := range project.Secrets { if s.Name == "" { - s.Name = fmt.Sprintf("%s_%s", project.Name, i) + s.Name = fmt.Sprintf("%s%s%s", project.Name, separator, i) project.Secrets[i] = s } } diff --git a/loader/normalize_test.go b/loader/normalize_test.go index c32a78ed..0b6aa84e 100644 --- a/loader/normalize_test.go +++ b/loader/normalize_test.go @@ -73,16 +73,16 @@ services: default: null networks: default: - name: myProject_default + name: myProject-default myExternalnet: name: myExternalnet external: true myNamedNet: name: CustomName mynet: - name: myProject_mynet + name: myProject-mynet ` - err := normalize(&project, false) + err := normalize(&project, false, DefaultSeparator) assert.NilError(t, err) marshal, err := yaml.Marshal(project) assert.NilError(t, err) @@ -116,9 +116,9 @@ services: default: null networks: default: - name: myProject_default + name: myProject-default `, filepath.Join(wd, "testdata")) - err := normalize(&project, true) + err := normalize(&project, true, DefaultSeparator) assert.NilError(t, err) marshal, err := yaml.Marshal(project) assert.NilError(t, err) @@ -138,11 +138,11 @@ func TestNormalizeAbsolutePaths(t *testing.T) { expected := types.Project{ Name: "myProject", - Networks: types.Networks{"default": {Name: "myProject_default"}}, + Networks: types.Networks{"default": {Name: "myProject-default"}}, WorkingDir: absWorkingDir, ComposeFiles: []string{absComposeFile, absOverrideFile}, } - err := normalize(&project, false) + err := normalize(&project, false, DefaultSeparator) assert.NilError(t, err) assert.DeepEqual(t, expected, project) } @@ -166,13 +166,13 @@ func TestNormalizeVolumes(t *testing.T) { absCwd, _ := filepath.Abs(".") expected := types.Project{ Name: "myProject", - Networks: types.Networks{"default": {Name: "myProject_default"}}, + Networks: types.Networks{"default": {Name: "myProject-default"}}, Volumes: types.Volumes{ "myExternalVol": { Name: "myExternalVol", External: types.External{External: true}, }, - "myvol": {Name: "myProject_myvol"}, + "myvol": {Name: "myProject-myvol"}, "myNamedVol": { Name: "CustomName", }, @@ -180,7 +180,29 @@ func TestNormalizeVolumes(t *testing.T) { WorkingDir: absCwd, ComposeFiles: []string{}, } - err := normalize(&project, false) + err := normalize(&project, false, DefaultSeparator) + assert.NilError(t, err) + assert.DeepEqual(t, expected, project) +} + +func TestNormalizeWithCompatibilitySeparator(t *testing.T) { + project := types.Project{ + Name: "myProject", + WorkingDir: "testdata", + Networks: types.Networks{}, + ComposeFiles: []string{filepath.Join("testdata", "simple", "compose.yaml"), filepath.Join("testdata", "simple", "compose-with-overrides.yaml")}, + } + absWorkingDir, _ := filepath.Abs("testdata") + absComposeFile, _ := filepath.Abs(filepath.Join("testdata", "simple", "compose.yaml")) + absOverrideFile, _ := filepath.Abs(filepath.Join("testdata", "simple", "compose-with-overrides.yaml")) + + expected := types.Project{ + Name: "myProject", + Networks: types.Networks{"default": {Name: "myProject_default"}}, + WorkingDir: absWorkingDir, + ComposeFiles: []string{absComposeFile, absOverrideFile}, + } + err := normalize(&project, false, "_") assert.NilError(t, err) assert.DeepEqual(t, expected, project) } From aa0ca5e25f4732981f33288829cb04ff727bd84e Mon Sep 17 00:00:00 2001 From: Guillaume Lours Date: Fri, 29 Jul 2022 15:45:44 +0200 Subject: [PATCH 2/2] use t.Setenv instead of os.SetEnv + defer os.Unsetenv Co-authored-by: Milas Bowman Signed-off-by: Guillaume Lours --- cli/options_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cli/options_test.go b/cli/options_test.go index d8a0bac5..2859d9da 100644 --- a/cli/options_test.go +++ b/cli/options_test.go @@ -251,8 +251,7 @@ func TestWithSeparator(t *testing.T) { }) t.Run("With compatibility separator", func(t *testing.T) { - os.Setenv("COMPOSE_COMPATIBILITY", "true") //nolint:errcheck - defer os.Unsetenv("COMPOSE_COMPATIBILITY") //nolint:errcheck + t.Setenv("COMPOSE_COMPATIBILITY", "true") opts, err := NewProjectOptions([]string{ "testdata/simple/compose-with-network-and-volume.yaml", }, WithName("my-project"), WithOsEnv)