Skip to content

Commit 760b0da

Browse files
authored
Merge pull request #239 from laurazard/add_windows_convert_paths
Implement convert windows paths functionality
2 parents 34fb4f3 + 4fbb481 commit 760b0da

File tree

6 files changed

+130
-5
lines changed

6 files changed

+130
-5
lines changed

cli/options.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/compose-spec/compose-go/errdefs"
2929
"github.com/compose-spec/compose-go/loader"
3030
"github.com/compose-spec/compose-go/types"
31+
"github.com/compose-spec/compose-go/utils"
3132
"github.com/pkg/errors"
3233
"github.com/sirupsen/logrus"
3334
)
@@ -332,7 +333,9 @@ func ProjectFromOptions(options *ProjectOptions) (*types.Project, error) {
332333
return nil, err
333334
}
334335

335-
options.loadOptions = append(options.loadOptions, withNamePrecedenceLoad(absWorkingDir, options))
336+
options.loadOptions = append(options.loadOptions,
337+
withNamePrecedenceLoad(absWorkingDir, options),
338+
withConvertWindowsPaths(options))
336339

337340
project, err := loader.Load(types.ConfigDetails{
338341
ConfigFiles: configs,
@@ -359,6 +362,12 @@ func withNamePrecedenceLoad(absWorkingDir string, options *ProjectOptions) func(
359362
}
360363
}
361364

365+
func withConvertWindowsPaths(options *ProjectOptions) func(*loader.Options) {
366+
return func(o *loader.Options) {
367+
o.ConvertWindowsPaths = utils.StringToBool(options.Environment["COMPOSE_CONVERT_WINDOWS_PATHS"])
368+
}
369+
}
370+
362371
// getConfigPathsFromOptions retrieves the config files for project based on project options
363372
func getConfigPathsFromOptions(options *ProjectOptions) ([]string, error) {
364373
if len(options.ConfigPaths) != 0 {

cli/options_windows_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
Copyright 2020 The Compose Specification Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cli
18+
19+
import (
20+
"os"
21+
"testing"
22+
23+
"gotest.tools/v3/assert"
24+
)
25+
26+
func TestConvertWithEnvVar(t *testing.T) {
27+
os.Setenv("COMPOSE_CONVERT_WINDOWS_PATHS", "1")
28+
defer os.Unsetenv("COMPOSE_CONVERT_WINDOWS_PATHS")
29+
opts, _ := NewProjectOptions([]string{"testdata/simple/compose-with-paths.yaml"}, WithOsEnv)
30+
31+
p, err := ProjectFromOptions(opts)
32+
33+
assert.NilError(t, err)
34+
assert.Equal(t, p.Services[0].Volumes[0].Source, "/c/docker/project")
35+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
services:
2+
test:
3+
image: hello-world
4+
volumes:
5+
- type: bind
6+
source: C:\docker\project
7+
target: /test

loader/loader.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ type Options struct {
5353
SkipNormalization bool
5454
// Resolve paths
5555
ResolvePaths bool
56+
// Convert Windows paths
57+
ConvertWindowsPaths bool
5658
// Skip consistency check
5759
SkipConsistencyCheck bool
5860
// Skip extends
@@ -489,7 +491,7 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
489491
return nil, fmt.Errorf("cannot extend service %q in %s: service not found", name, filename)
490492
}
491493

492-
serviceConfig, err := LoadService(name, target.(map[string]interface{}), workingDir, lookupEnv, opts.ResolvePaths)
494+
serviceConfig, err := LoadService(name, target.(map[string]interface{}), workingDir, lookupEnv, opts.ResolvePaths, opts.ConvertWindowsPaths)
493495
if err != nil {
494496
return nil, err
495497
}
@@ -552,7 +554,7 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
552554

553555
// LoadService produces a single ServiceConfig from a compose file Dict
554556
// the serviceDict is not validated if directly used. Use Load() to enable validation
555-
func LoadService(name string, serviceDict map[string]interface{}, workingDir string, lookupEnv template.Mapping, resolvePaths bool) (*types.ServiceConfig, error) {
557+
func LoadService(name string, serviceDict map[string]interface{}, workingDir string, lookupEnv template.Mapping, resolvePaths bool, convertPaths bool) (*types.ServiceConfig, error) {
556558
serviceConfig := &types.ServiceConfig{
557559
Scale: 1,
558560
}
@@ -577,11 +579,30 @@ func LoadService(name string, serviceDict map[string]interface{}, workingDir str
577579
if resolvePaths {
578580
serviceConfig.Volumes[i] = resolveVolumePath(volume, workingDir, lookupEnv)
579581
}
582+
583+
if convertPaths {
584+
serviceConfig.Volumes[i] = convertVolumePath(volume)
585+
}
580586
}
581587

582588
return serviceConfig, nil
583589
}
584590

591+
// Windows paths, c:\\my\\path\\shiny, need to be changed to be compatible with
592+
// the Engine. Volume paths are expected to be linux style /c/my/path/shiny/
593+
func convertVolumePath(volume types.ServiceVolumeConfig) types.ServiceVolumeConfig {
594+
volumeName := strings.ToLower(filepath.VolumeName(volume.Source))
595+
if len(volumeName) != 2 {
596+
return volume
597+
}
598+
599+
convertedSource := fmt.Sprintf("/%c%s", volumeName[0], volume.Source[len(volumeName):])
600+
convertedSource = strings.ReplaceAll(convertedSource, "\\", "/")
601+
602+
volume.Source = convertedSource
603+
return volume
604+
}
605+
585606
func resolveEnvironment(serviceConfig *types.ServiceConfig, workingDir string, lookupEnv template.Mapping) error {
586607
environment := types.MappingWithEquals{}
587608

loader/loader_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1833,7 +1833,7 @@ func TestLoadServiceWithEnvFile(t *testing.T) {
18331833
s, err := LoadService("Test Name", m, ".", func(s string) (string, bool) {
18341834
assert.Equal(t, "TEST", s)
18351835
return "YES", true
1836-
}, true)
1836+
}, true, false)
18371837
assert.NilError(t, err)
18381838
assert.Equal(t, "YES", *s.Environment["HALLO"])
18391839
}
@@ -1857,7 +1857,7 @@ func TestLoadServiceWithVolumes(t *testing.T) {
18571857
},
18581858
},
18591859
}
1860-
s, err := LoadService("Test Name", m, ".", nil, true)
1860+
s, err := LoadService("Test Name", m, ".", nil, true, false)
18611861
assert.NilError(t, err)
18621862
assert.Equal(t, len(s.Volumes), 2)
18631863
assert.Equal(t, "/path 1", s.Volumes[0].Target)

loader/loader_windows_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
Copyright 2020 The Compose Specification Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package loader
18+
19+
import (
20+
"testing"
21+
22+
"github.com/compose-spec/compose-go/types"
23+
"gotest.tools/v3/assert"
24+
)
25+
26+
func TestConvertWindowsVolumePath(t *testing.T) {
27+
var testcases = []struct {
28+
windowsPath string
29+
expectedConvertedPath string
30+
}{
31+
{
32+
windowsPath: "c:\\hello\\docker",
33+
expectedConvertedPath: "/c/hello/docker",
34+
},
35+
{
36+
windowsPath: "d:\\compose",
37+
expectedConvertedPath: "/d/compose",
38+
},
39+
{
40+
windowsPath: "e:\\path with spaces\\compose",
41+
expectedConvertedPath: "/e/path with spaces/compose",
42+
},
43+
}
44+
for _, testcase := range testcases {
45+
volume := types.ServiceVolumeConfig{
46+
Type: "bind",
47+
Source: testcase.windowsPath,
48+
Target: "/test",
49+
}
50+
51+
assert.Equal(t, testcase.expectedConvertedPath, convertVolumePath(volume).Source)
52+
}
53+
}

0 commit comments

Comments
 (0)