Skip to content

Commit 938b039

Browse files
authored
Merge pull request #178 from ndeloof/resolve_paths
2 parents feb401c + 2021abc commit 938b039

File tree

5 files changed

+64
-49
lines changed

5 files changed

+64
-49
lines changed

cli/options.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,16 @@ func WithInterpolation(interpolation bool) ProjectOptionsFn {
248248
}
249249
}
250250

251+
// WithResolvedPaths set ProjectOptions to enable paths resolution
252+
func WithResolvedPaths(resolve bool) ProjectOptionsFn {
253+
return func(o *ProjectOptions) error {
254+
o.loadOptions = append(o.loadOptions, func(options *loader.Options) {
255+
options.ResolvePaths = resolve
256+
})
257+
return nil
258+
}
259+
}
260+
251261
// DefaultFileNames defines the Compose file names for auto-discovery (in order of preference)
252262
var DefaultFileNames = []string{"compose.yaml", "compose.yml", "docker-compose.yml", "docker-compose.yaml"}
253263

loader/loader.go

Lines changed: 40 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ type Options struct {
4848
SkipInterpolation bool
4949
// Skip normalization
5050
SkipNormalization bool
51+
// Resolve paths
52+
ResolvePaths bool
5153
// Skip consistency check
5254
SkipConsistencyCheck bool
5355
// Skip extends
@@ -203,7 +205,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
203205
}
204206

205207
if !opts.SkipNormalization {
206-
err = normalize(project)
208+
err = normalize(project, opts.ResolvePaths)
207209
if err != nil {
208210
return nil, err
209211
}
@@ -266,11 +268,11 @@ func loadSections(filename string, config map[string]interface{}, configDetails
266268
if err != nil {
267269
return nil, err
268270
}
269-
cfg.Secrets, err = LoadSecrets(getSection(config, "secrets"), configDetails)
271+
cfg.Secrets, err = LoadSecrets(getSection(config, "secrets"), configDetails, opts.ResolvePaths)
270272
if err != nil {
271273
return nil, err
272274
}
273-
cfg.Configs, err = LoadConfigObjs(getSection(config, "configs"), configDetails)
275+
cfg.Configs, err = LoadConfigObjs(getSection(config, "configs"), configDetails, opts.ResolvePaths)
274276
if err != nil {
275277
return nil, err
276278
}
@@ -448,7 +450,7 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
448450
return nil, err
449451
}
450452

451-
serviceConfig, err := LoadService(name, servicesDict[name].(map[string]interface{}), workingDir, lookupEnv)
453+
serviceConfig, err := LoadService(name, servicesDict[name].(map[string]interface{}), workingDir, lookupEnv, opts.ResolvePaths)
452454
if err != nil {
453455
return nil, err
454456
}
@@ -519,7 +521,7 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
519521

520522
// LoadService produces a single ServiceConfig from a compose file Dict
521523
// the serviceDict is not validated if directly used. Use Load() to enable validation
522-
func LoadService(name string, serviceDict map[string]interface{}, workingDir string, lookupEnv template.Mapping) (*types.ServiceConfig, error) {
524+
func LoadService(name string, serviceDict map[string]interface{}, workingDir string, lookupEnv template.Mapping, resolvePaths bool) (*types.ServiceConfig, error) {
523525
serviceConfig := &types.ServiceConfig{}
524526
if err := Transform(serviceDict, serviceConfig); err != nil {
525527
return nil, err
@@ -530,8 +532,18 @@ func LoadService(name string, serviceDict map[string]interface{}, workingDir str
530532
return nil, err
531533
}
532534

533-
if err := resolveVolumePaths(serviceConfig.Volumes, workingDir, lookupEnv); err != nil {
534-
return nil, err
535+
for i, volume := range serviceConfig.Volumes {
536+
if volume.Type != "bind" {
537+
continue
538+
}
539+
540+
if volume.Source == "" {
541+
return nil, errors.New(`invalid mount config for type "bind": field Source must not be empty`)
542+
}
543+
544+
if resolvePaths {
545+
serviceConfig.Volumes[i] = resolveVolumePath(volume, workingDir, lookupEnv)
546+
}
535547
}
536548

537549
return serviceConfig, nil
@@ -566,30 +578,19 @@ func resolveEnvironment(serviceConfig *types.ServiceConfig, workingDir string, l
566578
return nil
567579
}
568580

569-
func resolveVolumePaths(volumes []types.ServiceVolumeConfig, workingDir string, lookupEnv template.Mapping) error {
570-
for i, volume := range volumes {
571-
if volume.Type != "bind" {
572-
continue
573-
}
574-
575-
if volume.Source == "" {
576-
return errors.New(`invalid mount config for type "bind": field Source must not be empty`)
577-
}
578-
579-
filePath := expandUser(volume.Source, lookupEnv)
580-
// Check if source is an absolute path (either Unix or Windows), to
581-
// handle a Windows client with a Unix daemon or vice-versa.
582-
//
583-
// Note that this is not required for Docker for Windows when specifying
584-
// a local Windows path, because Docker for Windows translates the Windows
585-
// path into a valid path within the VM.
586-
if !path.IsAbs(filePath) && !isAbs(filePath) {
587-
filePath = absPath(workingDir, filePath)
588-
}
589-
volume.Source = filePath
590-
volumes[i] = volume
591-
}
592-
return nil
581+
func resolveVolumePath(volume types.ServiceVolumeConfig, workingDir string, lookupEnv template.Mapping) types.ServiceVolumeConfig {
582+
filePath := expandUser(volume.Source, lookupEnv)
583+
// Check if source is an absolute path (either Unix or Windows), to
584+
// handle a Windows client with a Unix daemon or vice-versa.
585+
//
586+
// Note that this is not required for Docker for Windows when specifying
587+
// a local Windows path, because Docker for Windows translates the Windows
588+
// path into a valid path within the VM.
589+
if !path.IsAbs(filePath) && !isAbs(filePath) {
590+
filePath = absPath(workingDir, filePath)
591+
}
592+
volume.Source = filePath
593+
return volume
593594
}
594595

595596
// TODO: make this more robust
@@ -693,13 +694,13 @@ func LoadVolumes(source map[string]interface{}) (map[string]types.VolumeConfig,
693694

694695
// LoadSecrets produces a SecretConfig map from a compose file Dict
695696
// the source Dict is not validated if directly used. Use Load() to enable validation
696-
func LoadSecrets(source map[string]interface{}, details types.ConfigDetails) (map[string]types.SecretConfig, error) {
697+
func LoadSecrets(source map[string]interface{}, details types.ConfigDetails, resolvePaths bool) (map[string]types.SecretConfig, error) {
697698
secrets := make(map[string]types.SecretConfig)
698699
if err := Transform(source, &secrets); err != nil {
699700
return secrets, err
700701
}
701702
for name, secret := range secrets {
702-
obj, err := loadFileObjectConfig(name, "secret", types.FileObjectConfig(secret), details)
703+
obj, err := loadFileObjectConfig(name, "secret", types.FileObjectConfig(secret), details, resolvePaths)
703704
if err != nil {
704705
return nil, err
705706
}
@@ -711,13 +712,13 @@ func LoadSecrets(source map[string]interface{}, details types.ConfigDetails) (ma
711712

712713
// LoadConfigObjs produces a ConfigObjConfig map from a compose file Dict
713714
// the source Dict is not validated if directly used. Use Load() to enable validation
714-
func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails) (map[string]types.ConfigObjConfig, error) {
715+
func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails, resolvePaths bool) (map[string]types.ConfigObjConfig, error) {
715716
configs := make(map[string]types.ConfigObjConfig)
716717
if err := Transform(source, &configs); err != nil {
717718
return configs, err
718719
}
719720
for name, config := range configs {
720-
obj, err := loadFileObjectConfig(name, "config", types.FileObjectConfig(config), details)
721+
obj, err := loadFileObjectConfig(name, "config", types.FileObjectConfig(config), details, resolvePaths)
721722
if err != nil {
722723
return nil, err
723724
}
@@ -727,7 +728,7 @@ func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails)
727728
return configs, nil
728729
}
729730

730-
func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfig, details types.ConfigDetails) (types.FileObjectConfig, error) {
731+
func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfig, details types.ConfigDetails, resolvePaths bool) (types.FileObjectConfig, error) {
731732
// if "external: true"
732733
switch {
733734
case obj.External.External:
@@ -750,7 +751,9 @@ func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfi
750751
return obj, errors.Errorf("%[1]s %[2]s: %[1]s.driver and %[1]s.file conflict; only use %[1]s.driver", objType, name)
751752
}
752753
default:
753-
obj.File = absPath(details.WorkingDir, obj.File)
754+
if resolvePaths {
755+
obj.File = absPath(details.WorkingDir, obj.File)
756+
}
754757
}
755758

756759
return obj, nil

loader/loader_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ func loadYAMLWithEnv(yaml string, env map[string]string) (*types.Project, error)
5555
return Load(buildConfigDetails(yaml, env), func(options *Options) {
5656
options.SkipConsistencyCheck = true
5757
options.SkipNormalization = true
58+
options.ResolvePaths = true
5859
})
5960
}
6061

@@ -1342,7 +1343,7 @@ func TestLoadSecretsWarnOnDeprecatedExternalNameVersion35(t *testing.T) {
13421343
},
13431344
}
13441345
details := types.ConfigDetails{}
1345-
secrets, err := LoadSecrets(source, details)
1346+
secrets, err := LoadSecrets(source, details, true)
13461347
assert.NilError(t, err)
13471348
expected := map[string]types.SecretConfig{
13481349
"foo": {

loader/normalize.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828
)
2929

3030
// normalize compose project by moving deprecated attributes to their canonical position and injecting implicit defaults
31-
func normalize(project *types.Project) error {
31+
func normalize(project *types.Project, resolvePaths bool) error {
3232
absWorkingDir, err := filepath.Abs(project.WorkingDir)
3333
if err != nil {
3434
return err
@@ -72,8 +72,10 @@ func normalize(project *types.Project) error {
7272
}
7373
localContext := absPath(project.WorkingDir, s.Build.Context)
7474
if _, err := os.Stat(localContext); err == nil {
75-
s.Build.Context = localContext
76-
s.Build.Dockerfile = absPath(localContext, s.Build.Dockerfile)
75+
if resolvePaths {
76+
s.Build.Context = localContext
77+
s.Build.Dockerfile = absPath(localContext, s.Build.Dockerfile)
78+
}
7779
} else {
7880
// might be a remote http/git context. Unfortunately supported "remote" syntax is highly ambiguous
7981
// in moby/moby and not defined by compose-spec, so let's assume runtime will check

loader/normalize_test.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package loader
1919
import (
2020
"os"
2121
"path/filepath"
22-
"strings"
2322
"testing"
2423

2524
"github.com/compose-spec/compose-go/types"
@@ -59,11 +58,11 @@ func TestNormalizeNetworkNames(t *testing.T) {
5958
},
6059
}
6160

62-
expected := strings.ReplaceAll(strings.ReplaceAll(`services:
61+
expected := `services:
6362
foo:
6463
build:
65-
context: /some/path/testdata
66-
dockerfile: /some/path/testdata/Dockerfile
64+
context: ./testdata
65+
dockerfile: Dockerfile
6766
args:
6867
FOO: BAR
6968
ZOT: null
@@ -79,8 +78,8 @@ networks:
7978
name: CustomName
8079
mynet:
8180
name: myProject_mynet
82-
`, "/some/path", wd), "/", string(filepath.Separator))
83-
err := normalize(&project)
81+
`
82+
err := normalize(&project, false)
8483
assert.NilError(t, err)
8584
marshal, err := yaml.Marshal(project)
8685
assert.NilError(t, err)
@@ -104,7 +103,7 @@ func TestNormalizeAbsolutePaths(t *testing.T) {
104103
WorkingDir: absWorkingDir,
105104
ComposeFiles: []string{absComposeFile, absOverrideFile},
106105
}
107-
err := normalize(&project)
106+
err := normalize(&project, false)
108107
assert.NilError(t, err)
109108
assert.DeepEqual(t, expected, project)
110109
}
@@ -142,7 +141,7 @@ func TestNormalizeVolumes(t *testing.T) {
142141
WorkingDir: absCwd,
143142
ComposeFiles: []string{},
144143
}
145-
err := normalize(&project)
144+
err := normalize(&project, false)
146145
assert.NilError(t, err)
147146
assert.DeepEqual(t, expected, project)
148147
}

0 commit comments

Comments
 (0)