Skip to content

Commit 0b97a90

Browse files
committed
introduce ResolveRelativePaths as a distinct operation vs Normalization
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent dc7471e commit 0b97a90

File tree

7 files changed

+317
-256
lines changed

7 files changed

+317
-256
lines changed

loader/full-struct_test.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,8 @@ func services(workingDir, homeDir string) []types.ServiceConfig {
177177
"ENV_WITH_UNDERSCORE": strPtr("ok"),
178178
},
179179
EnvFile: []string{
180-
"./example1.env",
181-
"./example2.env",
180+
filepath.Join(workingDir, "example1.env"),
181+
filepath.Join(workingDir, "example2.env"),
182182
},
183183
Expose: []string{"3000", "8000"},
184184
ExternalLinks: []string{
@@ -728,8 +728,8 @@ services:
728728
FOO: foo_from_env_file
729729
QUX: qux_from_environment
730730
env_file:
731-
- ./example1.env
732-
- ./example2.env
731+
- %s
732+
- %s
733733
expose:
734734
- "3000"
735735
- "8000"
@@ -1035,6 +1035,8 @@ x-nested:
10351035
bar: baz
10361036
foo: bar
10371037
`,
1038+
filepath.Join(workingDir, "example1.env"),
1039+
filepath.Join(workingDir, "example2.env"),
10381040
workingDir,
10391041
filepath.Join(workingDir, "static"),
10401042
filepath.Join(homeDir, "configs"),
@@ -1327,8 +1329,8 @@ func fullExampleJSON(workingDir, homeDir string) string {
13271329
"QUX": "qux_from_environment"
13281330
},
13291331
"env_file": [
1330-
"./example1.env",
1331-
"./example2.env"
1332+
"%s",
1333+
"%s"
13321334
],
13331335
"expose": [
13341336
"3000",
@@ -1677,6 +1679,8 @@ func fullExampleJSON(workingDir, homeDir string) string {
16771679
toPath(workingDir, "config_data"),
16781680
toPath(homeDir, "config_data"),
16791681
toPath(workingDir, "secret_data"),
1682+
toPath(workingDir, "example1.env"),
1683+
toPath(workingDir, "example2.env"),
16801684
toPath(workingDir),
16811685
toPath(workingDir, "static"),
16821686
toPath(homeDir, "configs"),

loader/loader.go

Lines changed: 35 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
185185
LookupValue: configDetails.LookupEnv,
186186
TypeCastMapping: interpolateTypeCastMapping,
187187
},
188+
ResolvePaths: true,
188189
}
189190

190191
for _, op := range options {
@@ -248,14 +249,6 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
248249
model = merged
249250
}
250251

251-
for _, s := range model.Services {
252-
var newEnvFiles types.StringList
253-
for _, ef := range s.EnvFile {
254-
newEnvFiles = append(newEnvFiles, absPath(configDetails.WorkingDir, ef))
255-
}
256-
s.EnvFile = newEnvFiles
257-
}
258-
259252
project := &types.Project{
260253
Name: projectName,
261254
WorkingDir: configDetails.WorkingDir,
@@ -268,8 +261,24 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
268261
Extensions: model.Extensions,
269262
}
270263

264+
if opts.ResolvePaths {
265+
err = ResolveRelativePaths(project)
266+
if err != nil {
267+
return nil, err
268+
}
269+
}
270+
271+
if opts.ConvertWindowsPaths {
272+
for i, service := range project.Services {
273+
for j, volume := range service.Volumes {
274+
service.Volumes[j] = convertVolumePath(volume)
275+
}
276+
project.Services[i] = service
277+
}
278+
}
279+
271280
if !opts.SkipNormalization {
272-
err = Normalize(project, opts.ResolvePaths)
281+
err = Normalize(project)
273282
if err != nil {
274283
return nil, err
275284
}
@@ -428,11 +437,11 @@ func loadSections(filename string, config map[string]interface{}, configDetails
428437
if err != nil {
429438
return nil, err
430439
}
431-
cfg.Secrets, err = LoadSecrets(getSection(config, "secrets"), configDetails, opts.ResolvePaths)
440+
cfg.Secrets, err = LoadSecrets(getSection(config, "secrets"))
432441
if err != nil {
433442
return nil, err
434443
}
435-
cfg.Configs, err = LoadConfigObjs(getSection(config, "configs"), configDetails, opts.ResolvePaths)
444+
cfg.Configs, err = LoadConfigObjs(getSection(config, "configs"))
436445
if err != nil {
437446
return nil, err
438447
}
@@ -634,7 +643,7 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
634643
target = map[string]interface{}{}
635644
}
636645

637-
serviceConfig, err := LoadService(name, target.(map[string]interface{}), workingDir, lookupEnv, opts.ResolvePaths, opts.ConvertWindowsPaths)
646+
serviceConfig, err := LoadService(name, target.(map[string]interface{}))
638647
if err != nil {
639648
return nil, err
640649
}
@@ -672,21 +681,9 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
672681
// make the paths relative to `file` rather than `baseFilePath` so
673682
// that the resulting paths won't be absolute if `file` isn't an
674683
// absolute path.
675-
baseFileParent := filepath.Dir(file)
676-
if baseService.Build != nil {
677-
baseService.Build.Context = resolveBuildContextPath(baseFileParent, baseService.Build.Context)
678-
}
679684

680-
for i, vol := range baseService.Volumes {
681-
if vol.Type != types.VolumeTypeBind {
682-
continue
683-
}
684-
baseService.Volumes[i].Source = resolveMaybeUnixPath(vol.Source, baseFileParent, lookupEnv)
685-
}
686-
687-
for i, envFile := range baseService.EnvFile {
688-
baseService.EnvFile[i] = resolveMaybeUnixPath(envFile, baseFileParent, lookupEnv)
689-
}
685+
baseFileParent := filepath.Dir(file)
686+
ResolveServiceRelativePaths(baseFileParent, baseService)
690687
}
691688

692689
serviceConfig, err = _merge(baseService, serviceConfig)
@@ -699,22 +696,9 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
699696
return serviceConfig, nil
700697
}
701698

702-
func resolveBuildContextPath(baseFileParent string, context string) string {
703-
// Checks if the context is an HTTP(S) URL or a remote git repository URL
704-
for _, prefix := range []string{"https://", "http://", "git://", "github.com/", "git@"} {
705-
if strings.HasPrefix(context, prefix) {
706-
return context
707-
}
708-
}
709-
710-
// Note that the Dockerfile is always defined relative to the
711-
// build context, so there's no need to update the Dockerfile field.
712-
return absPath(baseFileParent, context)
713-
}
714-
715699
// LoadService produces a single ServiceConfig from a compose file Dict
716700
// the serviceDict is not validated if directly used. Use Load() to enable validation
717-
func LoadService(name string, serviceDict map[string]interface{}, workingDir string, lookupEnv template.Mapping, resolvePaths bool, convertPaths bool) (*types.ServiceConfig, error) {
701+
func LoadService(name string, serviceDict map[string]interface{}) (*types.ServiceConfig, error) {
718702
serviceConfig := &types.ServiceConfig{
719703
Scale: 1,
720704
}
@@ -731,13 +715,6 @@ func LoadService(name string, serviceDict map[string]interface{}, workingDir str
731715
return nil, errors.New(`invalid mount config for type "bind": field Source must not be empty`)
732716
}
733717

734-
if resolvePaths || convertPaths {
735-
volume = resolveVolumePath(volume, workingDir, lookupEnv)
736-
}
737-
738-
if convertPaths {
739-
volume = convertVolumePath(volume)
740-
}
741718
serviceConfig.Volumes[i] = volume
742719
}
743720

@@ -759,8 +736,8 @@ func convertVolumePath(volume types.ServiceVolumeConfig) types.ServiceVolumeConf
759736
return volume
760737
}
761738

762-
func resolveMaybeUnixPath(path string, workingDir string, lookupEnv template.Mapping) string {
763-
filePath := expandUser(path, lookupEnv)
739+
func resolveMaybeUnixPath(workingDir string, path string) string {
740+
filePath := expandUser(path)
764741
// Check if source is an absolute path (either Unix or Windows), to
765742
// handle a Windows client with a Unix daemon or vice-versa.
766743
//
@@ -773,20 +750,15 @@ func resolveMaybeUnixPath(path string, workingDir string, lookupEnv template.Map
773750
return filePath
774751
}
775752

776-
func resolveVolumePath(volume types.ServiceVolumeConfig, workingDir string, lookupEnv template.Mapping) types.ServiceVolumeConfig {
777-
volume.Source = resolveMaybeUnixPath(volume.Source, workingDir, lookupEnv)
778-
return volume
779-
}
780-
781753
func resolveSecretsPath(secret types.SecretConfig, workingDir string, lookupEnv template.Mapping) types.SecretConfig {
782754
if !secret.External.External && secret.File != "" {
783-
secret.File = resolveMaybeUnixPath(secret.File, workingDir, lookupEnv)
755+
secret.File = resolveMaybeUnixPath(workingDir, secret.File)
784756
}
785757
return secret
786758
}
787759

788760
// TODO: make this more robust
789-
func expandUser(path string, lookupEnv template.Mapping) string {
761+
func expandUser(path string) string {
790762
if strings.HasPrefix(path, "~") {
791763
home, err := os.UserHomeDir()
792764
if err != nil {
@@ -886,44 +858,39 @@ func LoadVolumes(source map[string]interface{}) (map[string]types.VolumeConfig,
886858

887859
// LoadSecrets produces a SecretConfig map from a compose file Dict
888860
// the source Dict is not validated if directly used. Use Load() to enable validation
889-
func LoadSecrets(source map[string]interface{}, details types.ConfigDetails, resolvePaths bool) (map[string]types.SecretConfig, error) {
861+
func LoadSecrets(source map[string]interface{}) (map[string]types.SecretConfig, error) {
890862
secrets := make(map[string]types.SecretConfig)
891863
if err := Transform(source, &secrets); err != nil {
892864
return secrets, err
893865
}
894866
for name, secret := range secrets {
895-
obj, err := loadFileObjectConfig(name, "secret", types.FileObjectConfig(secret), details, false)
867+
obj, err := loadFileObjectConfig(name, "secret", types.FileObjectConfig(secret))
896868
if err != nil {
897869
return nil, err
898870
}
899-
secretConfig := types.SecretConfig(obj)
900-
if resolvePaths {
901-
secretConfig = resolveSecretsPath(secretConfig, details.WorkingDir, details.LookupEnv)
902-
}
903-
secrets[name] = secretConfig
871+
secrets[name] = types.SecretConfig(obj)
904872
}
905873
return secrets, nil
906874
}
907875

908876
// LoadConfigObjs produces a ConfigObjConfig map from a compose file Dict
909877
// the source Dict is not validated if directly used. Use Load() to enable validation
910-
func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails, resolvePaths bool) (map[string]types.ConfigObjConfig, error) {
878+
func LoadConfigObjs(source map[string]interface{}) (map[string]types.ConfigObjConfig, error) {
911879
configs := make(map[string]types.ConfigObjConfig)
912880
if err := Transform(source, &configs); err != nil {
913881
return configs, err
914882
}
915883
for name, config := range configs {
916-
obj, err := loadFileObjectConfig(name, "config", types.FileObjectConfig(config), details, resolvePaths)
884+
obj, err := loadFileObjectConfig(name, "config", types.FileObjectConfig(config))
917885
if err != nil {
918886
return nil, err
919887
}
920-
configConfig := types.ConfigObjConfig(obj)
921-
configs[name] = configConfig
888+
configs[name] = types.ConfigObjConfig(obj)
922889
}
923890
return configs, nil
924891
}
925892

926-
func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfig, details types.ConfigDetails, resolvePaths bool) (types.FileObjectConfig, error) {
893+
func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfig) (types.FileObjectConfig, error) {
927894
// if "external: true"
928895
switch {
929896
case obj.External.External:
@@ -943,26 +910,11 @@ func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfi
943910
if obj.File != "" {
944911
return obj, errors.Errorf("%[1]s %[2]s: %[1]s.driver and %[1]s.file conflict; only use %[1]s.driver", objType, name)
945912
}
946-
default:
947-
if obj.File != "" && resolvePaths {
948-
obj.File = absPath(details.WorkingDir, obj.File)
949-
}
950913
}
951914

952915
return obj, nil
953916
}
954917

955-
func absPath(workingDir string, filePath string) string {
956-
if strings.HasPrefix(filePath, "~") {
957-
home, _ := os.UserHomeDir()
958-
return filepath.Join(home, filePath[1:])
959-
}
960-
if filepath.IsAbs(filePath) {
961-
return filePath
962-
}
963-
return filepath.Join(workingDir, filePath)
964-
}
965-
966918
var transformMapStringString TransformerFunc = func(data interface{}) (interface{}, error) {
967919
switch value := data.(type) {
968920
case map[string]interface{}:

loader/loader_test.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,7 @@ services:
843843
// Default behavior keeps the `env_file` entries
844844
configWithEnvFiles, err := Load(configDetails, func(options *Options) {
845845
options.SkipNormalization = true
846+
options.ResolvePaths = false
846847
})
847848
assert.NilError(t, err)
848849
assert.DeepEqual(t, configWithEnvFiles.Services[0].EnvFile, types.StringList{"example1.env",
@@ -1453,8 +1454,7 @@ func TestLoadSecretsWarnOnDeprecatedExternalNameVersion35(t *testing.T) {
14531454
},
14541455
},
14551456
}
1456-
details := types.ConfigDetails{}
1457-
secrets, err := LoadSecrets(source, details, true)
1457+
secrets, err := LoadSecrets(source)
14581458
assert.NilError(t, err)
14591459
expected := map[string]types.SecretConfig{
14601460
"foo": {
@@ -1929,8 +1929,7 @@ func TestLoadWithExtends(t *testing.T) {
19291929
actual, err := Load(configDetails)
19301930
assert.NilError(t, err)
19311931

1932-
extendsDir, err := filepath.Abs(filepath.Join("testdata", "subdir"))
1933-
assert.NilError(t, err)
1932+
extendsDir := filepath.Join("testdata", "subdir")
19341933

19351934
expectedEnvFilePath := filepath.Join(extendsDir, "extra.env")
19361935

@@ -2089,7 +2088,7 @@ func TestLoadServiceWithVolumes(t *testing.T) {
20892088
},
20902089
},
20912090
}
2092-
s, err := LoadService("Test Name", m, ".", nil, true, false)
2091+
s, err := LoadService("Test Name", m)
20932092
assert.NilError(t, err)
20942093
assert.Equal(t, len(s.Volumes), 2)
20952094
assert.Equal(t, "/path 1", s.Volumes[0].Target)

0 commit comments

Comments
 (0)