Skip to content

Commit 2021abc

Browse files
committed
introduce WithResolvedPaths to let client code opt for relative or absolute paths
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent c78dcff commit 2021abc

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
@@ -240,6 +240,16 @@ func WithInterpolation(interpolation bool) ProjectOptionsFn {
240240
}
241241
}
242242

243+
// WithResolvedPaths set ProjectOptions to enable paths resolution
244+
func WithResolvedPaths(resolve bool) ProjectOptionsFn {
245+
return func(o *ProjectOptions) error {
246+
o.loadOptions = append(o.loadOptions, func(options *loader.Options) {
247+
options.ResolvePaths = resolve
248+
})
249+
return nil
250+
}
251+
}
252+
243253
// DefaultFileNames defines the Compose file names for auto-discovery (in order of preference)
244254
var DefaultFileNames = []string{"compose.yaml", "compose.yml", "docker-compose.yml", "docker-compose.yaml"}
245255

loader/loader.go

Lines changed: 40 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ type Options struct {
4949
SkipInterpolation bool
5050
// Skip normalization
5151
SkipNormalization bool
52+
// Resolve paths
53+
ResolvePaths bool
5254
// Skip consistency check
5355
SkipConsistencyCheck bool
5456
// Skip extends
@@ -199,7 +201,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
199201
}
200202

201203
if !opts.SkipNormalization {
202-
err = normalize(project)
204+
err = normalize(project, opts.ResolvePaths)
203205
if err != nil {
204206
return nil, err
205207
}
@@ -269,11 +271,11 @@ func loadSections(filename string, config map[string]interface{}, configDetails
269271
if err != nil {
270272
return nil, err
271273
}
272-
cfg.Secrets, err = LoadSecrets(getSection(config, "secrets"), configDetails)
274+
cfg.Secrets, err = LoadSecrets(getSection(config, "secrets"), configDetails, opts.ResolvePaths)
273275
if err != nil {
274276
return nil, err
275277
}
276-
cfg.Configs, err = LoadConfigObjs(getSection(config, "configs"), configDetails)
278+
cfg.Configs, err = LoadConfigObjs(getSection(config, "configs"), configDetails, opts.ResolvePaths)
277279
if err != nil {
278280
return nil, err
279281
}
@@ -443,7 +445,7 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
443445
return nil, err
444446
}
445447

446-
serviceConfig, err := LoadService(name, servicesDict[name].(map[string]interface{}), workingDir, lookupEnv)
448+
serviceConfig, err := LoadService(name, servicesDict[name].(map[string]interface{}), workingDir, lookupEnv, opts.ResolvePaths)
447449
if err != nil {
448450
return nil, err
449451
}
@@ -514,7 +516,7 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
514516

515517
// LoadService produces a single ServiceConfig from a compose file Dict
516518
// the serviceDict is not validated if directly used. Use Load() to enable validation
517-
func LoadService(name string, serviceDict map[string]interface{}, workingDir string, lookupEnv template.Mapping) (*types.ServiceConfig, error) {
519+
func LoadService(name string, serviceDict map[string]interface{}, workingDir string, lookupEnv template.Mapping, resolvePaths bool) (*types.ServiceConfig, error) {
518520
serviceConfig := &types.ServiceConfig{}
519521
if err := Transform(serviceDict, serviceConfig); err != nil {
520522
return nil, err
@@ -525,8 +527,18 @@ func LoadService(name string, serviceDict map[string]interface{}, workingDir str
525527
return nil, err
526528
}
527529

528-
if err := resolveVolumePaths(serviceConfig.Volumes, workingDir, lookupEnv); err != nil {
529-
return nil, err
530+
for i, volume := range serviceConfig.Volumes {
531+
if volume.Type != "bind" {
532+
continue
533+
}
534+
535+
if volume.Source == "" {
536+
return nil, errors.New(`invalid mount config for type "bind": field Source must not be empty`)
537+
}
538+
539+
if resolvePaths {
540+
serviceConfig.Volumes[i] = resolveVolumePath(volume, workingDir, lookupEnv)
541+
}
530542
}
531543

532544
return serviceConfig, nil
@@ -561,30 +573,19 @@ func resolveEnvironment(serviceConfig *types.ServiceConfig, workingDir string, l
561573
return nil
562574
}
563575

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

590591
// TODO: make this more robust
@@ -688,13 +689,13 @@ func LoadVolumes(source map[string]interface{}) (map[string]types.VolumeConfig,
688689

689690
// LoadSecrets produces a SecretConfig map from a compose file Dict
690691
// the source Dict is not validated if directly used. Use Load() to enable validation
691-
func LoadSecrets(source map[string]interface{}, details types.ConfigDetails) (map[string]types.SecretConfig, error) {
692+
func LoadSecrets(source map[string]interface{}, details types.ConfigDetails, resolvePaths bool) (map[string]types.SecretConfig, error) {
692693
secrets := make(map[string]types.SecretConfig)
693694
if err := Transform(source, &secrets); err != nil {
694695
return secrets, err
695696
}
696697
for name, secret := range secrets {
697-
obj, err := loadFileObjectConfig(name, "secret", types.FileObjectConfig(secret), details)
698+
obj, err := loadFileObjectConfig(name, "secret", types.FileObjectConfig(secret), details, resolvePaths)
698699
if err != nil {
699700
return nil, err
700701
}
@@ -706,13 +707,13 @@ func LoadSecrets(source map[string]interface{}, details types.ConfigDetails) (ma
706707

707708
// LoadConfigObjs produces a ConfigObjConfig map from a compose file Dict
708709
// the source Dict is not validated if directly used. Use Load() to enable validation
709-
func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails) (map[string]types.ConfigObjConfig, error) {
710+
func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails, resolvePaths bool) (map[string]types.ConfigObjConfig, error) {
710711
configs := make(map[string]types.ConfigObjConfig)
711712
if err := Transform(source, &configs); err != nil {
712713
return configs, err
713714
}
714715
for name, config := range configs {
715-
obj, err := loadFileObjectConfig(name, "config", types.FileObjectConfig(config), details)
716+
obj, err := loadFileObjectConfig(name, "config", types.FileObjectConfig(config), details, resolvePaths)
716717
if err != nil {
717718
return nil, err
718719
}
@@ -722,7 +723,7 @@ func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails)
722723
return configs, nil
723724
}
724725

725-
func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfig, details types.ConfigDetails) (types.FileObjectConfig, error) {
726+
func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfig, details types.ConfigDetails, resolvePaths bool) (types.FileObjectConfig, error) {
726727
// if "external: true"
727728
switch {
728729
case obj.External.External:
@@ -745,7 +746,9 @@ func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfi
745746
return obj, errors.Errorf("%[1]s %[2]s: %[1]s.driver and %[1]s.file conflict; only use %[1]s.driver", objType, name)
746747
}
747748
default:
748-
obj.File = absPath(details.WorkingDir, obj.File)
749+
if resolvePaths {
750+
obj.File = absPath(details.WorkingDir, obj.File)
751+
}
749752
}
750753

751754
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

@@ -1339,7 +1340,7 @@ func TestLoadSecretsWarnOnDeprecatedExternalNameVersion35(t *testing.T) {
13391340
},
13401341
}
13411342
details := types.ConfigDetails{}
1342-
secrets, err := LoadSecrets(source, details)
1343+
secrets, err := LoadSecrets(source, details, true)
13431344
assert.NilError(t, err)
13441345
expected := map[string]types.SecretConfig{
13451346
"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)