Skip to content

Commit a66c385

Browse files
committed
Handle * when resolving paths in watch path
Signed-off-by: Joana Hrotkó <[email protected]>
1 parent f9189f8 commit a66c385

File tree

8 files changed

+131
-51
lines changed

8 files changed

+131
-51
lines changed

loader/loader_test.go

+80-45
Original file line numberDiff line numberDiff line change
@@ -3051,7 +3051,8 @@ services:
30513051
}
30523052

30533053
func TestLoadDevelopConfig(t *testing.T) {
3054-
project, err := LoadWithContext(context.TODO(), buildConfigDetails(`
3054+
t.Run("successfully load watch config", func(t *testing.T) {
3055+
project, err := LoadWithContext(context.Background(), buildConfigDetails(`
30553056
name: load-develop
30563057
services:
30573058
frontend:
@@ -3066,7 +3067,6 @@ services:
30663067
target: /var/www
30673068
ignore:
30683069
- node_modules/
3069-
30703070
backend:
30713071
image: example/backend
30723072
build: ./backend
@@ -3077,7 +3077,6 @@ services:
30773077
path:
30783078
- ./backend/src
30793079
- ./backend
3080-
30813080
proxy:
30823081
image: example/proxy
30833082
build: ./proxy
@@ -3088,67 +3087,103 @@ services:
30883087
action: sync+restart
30893088
target: /etc/nginx/proxy.conf
30903089
`, nil), func(options *Options) {
3091-
options.ResolvePaths = false
3092-
options.SkipValidation = true
3093-
})
3094-
assert.NilError(t, err)
3095-
frontend, err := project.GetService("frontend")
3096-
assert.NilError(t, err)
3097-
assert.DeepEqual(t, *frontend.Develop, types.DevelopConfig{
3098-
Watch: []types.Trigger{
3099-
{
3100-
Path: []string{"./webapp/html"},
3101-
Action: types.WatchActionSync,
3102-
Target: "/var/www",
3103-
Ignore: []string{"node_modules/"},
3104-
Extensions: types.Extensions{
3105-
"x-initialSync": true,
3090+
options.ResolvePaths = false
3091+
options.SkipValidation = true
3092+
})
3093+
assert.NilError(t, err)
3094+
frontend, err := project.GetService("frontend")
3095+
assert.NilError(t, err)
3096+
assert.DeepEqual(t, *frontend.Develop, types.DevelopConfig{
3097+
Watch: []types.Trigger{
3098+
{
3099+
Path: []string{"./webapp/html"},
3100+
Action: types.WatchActionSync,
3101+
Target: "/var/www",
3102+
Ignore: []string{"node_modules/"},
3103+
Extensions: types.Extensions{
3104+
"x-initialSync": true,
3105+
},
31063106
},
31073107
},
3108-
},
3109-
})
3110-
backend, err := project.GetService("backend")
3111-
assert.NilError(t, err)
3112-
assert.DeepEqual(t, *backend.Develop, types.DevelopConfig{
3113-
Watch: []types.Trigger{
3114-
{
3115-
Path: []string{"./backend/src", "./backend"},
3116-
Action: types.WatchActionRebuild,
3108+
})
3109+
backend, err := project.GetService("backend")
3110+
assert.NilError(t, err)
3111+
assert.DeepEqual(t, *backend.Develop, types.DevelopConfig{
3112+
Watch: []types.Trigger{
3113+
{
3114+
Path: []string{"./backend/src", "./backend"},
3115+
Action: types.WatchActionRebuild,
3116+
},
31173117
},
3118-
},
3119-
})
3120-
proxy, err := project.GetService("proxy")
3121-
assert.NilError(t, err)
3122-
assert.DeepEqual(t, *proxy.Develop, types.DevelopConfig{
3123-
Watch: []types.Trigger{
3124-
{
3125-
Path: []string{"./proxy/proxy.conf"},
3126-
Action: types.WatchActionSyncRestart,
3127-
Target: "/etc/nginx/proxy.conf",
3118+
})
3119+
proxy, err := project.GetService("proxy")
3120+
assert.NilError(t, err)
3121+
assert.DeepEqual(t, *proxy.Develop, types.DevelopConfig{
3122+
Watch: []types.Trigger{
3123+
{
3124+
Path: []string{"./proxy/proxy.conf"},
3125+
Action: types.WatchActionSyncRestart,
3126+
Target: "/etc/nginx/proxy.conf",
3127+
},
31283128
},
3129-
},
3129+
})
31303130
})
3131-
}
31323131

3133-
func TestBadDevelopConfig(t *testing.T) {
3134-
_, err := LoadWithContext(context.TODO(), buildConfigDetails(`
3132+
t.Run("should not load successfully bad watch config", func(t *testing.T) {
3133+
_, err := LoadWithContext(context.TODO(), buildConfigDetails(`
31353134
name: load-develop
31363135
services:
31373136
frontend:
31383137
image: example/webapp
31393138
build: ./webapp
31403139
develop:
31413140
watch:
3142-
# sync static content
3141+
# sync static content
31433142
- path: ./webapp/html
31443143
target: /var/www
31453144
ignore:
31463145
- node_modules/
3147-
31483146
`, nil), func(options *Options) {
3149-
options.ResolvePaths = false
3147+
options.ResolvePaths = false
3148+
})
3149+
assert.ErrorContains(t, err, "validating filename0.yml: services.frontend.develop.watch.0 action is required")
3150+
})
3151+
3152+
t.Run("should return an error when cannot resolve path", func(t *testing.T) {
3153+
b, err := os.ReadFile("testdata/watch/compose-test-watch-star.yaml")
3154+
assert.NilError(t, err)
3155+
3156+
configDetails := types.ConfigDetails{
3157+
WorkingDir: "testdata",
3158+
ConfigFiles: []types.ConfigFile{
3159+
{Filename: "watch/compose-test-watch-star.yaml", Content: b},
3160+
},
3161+
Environment: map[string]string{},
3162+
}
3163+
expServices := types.Services{
3164+
"app": {
3165+
Name: "app",
3166+
Image: "example/app",
3167+
Environment: types.MappingWithEquals{},
3168+
Networks: map[string]*types.ServiceNetworkConfig{"default": nil},
3169+
Develop: &types.DevelopConfig{
3170+
Watch: []types.Trigger{
3171+
{
3172+
Path: []string{
3173+
filepath.FromSlash("testdata/watch/other.txt"),
3174+
filepath.FromSlash("testdata/watch/some-text.txt"),
3175+
},
3176+
Action: types.WatchActionRebuild,
3177+
},
3178+
},
3179+
},
3180+
},
3181+
}
3182+
3183+
actual, err := LoadWithContext(context.Background(), configDetails)
3184+
assert.NilError(t, err)
3185+
assert.DeepEqual(t, actual.Services, expServices)
31503186
})
3151-
assert.ErrorContains(t, err, "validating filename0.yml: services.frontend.develop.watch.0 action is required")
31523187
}
31533188

31543189
func TestBadServiceConfig(t *testing.T) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
name: compose-test-watch-star
2+
services:
3+
app:
4+
image: example/app
5+
develop:
6+
watch:
7+
- path: ./watch/*.txt
8+
action: rebuild
9+
# - path: ./watch/*
10+
# target: ./app
11+
# action: sync

loader/testdata/watch/other.txt

Whitespace-only changes.

loader/testdata/watch/some-text.txt

Whitespace-only changes.

loader/validate.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ func checkConsistency(project *types.Project) error { //nolint:gocyclo
173173

174174
}
175175
if len(watch.Path) > 1 {
176-
return fmt.Errorf("services.%s.develop.watch: can only use more than one path for actions %s and %s: %w", s.Name, types.WatchActionRebuild, types.WatchActionRestart, errdefs.ErrInvalid)
176+
return fmt.Errorf("services.%s.develop.watch: detected multiple paths %s for action %s. Multiple files are only valid for %s and %s actions: %w", s.Name, watch.Path, watch.Action, types.WatchActionRebuild, types.WatchActionRestart, errdefs.ErrInvalid)
177177
}
178178
}
179179
}

loader/validate_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ func TestValidateWatch(t *testing.T) {
395395
},
396396
}
397397
err := checkConsistency(&project)
398-
assert.Error(t, err, "services.myservice.develop.watch: can only use more than one path for actions rebuild and restart: invalid compose project")
398+
assert.ErrorContains(t, err, "services.myservice.develop.watch: detected multiple paths")
399399
})
400400
}
401401
tests = []WatchActionTest{

paths/unix.go

+37-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package paths
1818

1919
import (
20+
"fmt"
2021
"path"
2122
"path/filepath"
2223

@@ -49,9 +50,41 @@ func (r *relativePathsResolver) absSymbolicLink(value any) (any, error) {
4950
if err != nil {
5051
return nil, err
5152
}
52-
str, ok := abs.(string)
53-
if !ok {
54-
return abs, nil
53+
switch t := abs.(type) {
54+
case string:
55+
// this can return []string if * matches more than one file
56+
return resolveAbsStarPath(t)
57+
case []any:
58+
var res []any
59+
for _, tt := range t {
60+
s, _ := tt.(string)
61+
r, err := resolveAbsStarPath(s)
62+
if err != nil {
63+
return nil, err
64+
}
65+
res = append(res, r...)
66+
}
67+
return res, nil
68+
}
69+
70+
return abs, nil
71+
}
72+
73+
func resolveAbsStarPath(t string) ([]any, error) {
74+
matches, err := filepath.Glob(t)
75+
if err != nil {
76+
return nil, err
77+
}
78+
if len(matches) == 0 {
79+
return nil, fmt.Errorf("could not resolve %s. Please make sure it exists?", t)
80+
}
81+
res := make([]any, len(matches))
82+
for i, m := range matches {
83+
symb, err := utils.ResolveSymbolicLink(m)
84+
if err != nil {
85+
return nil, err
86+
}
87+
res[i] = symb
5588
}
56-
return utils.ResolveSymbolicLink(str)
89+
return res, nil
5790
}

transform/canonical.go

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func init() {
3636
transformers["services.*.networks"] = transformServiceNetworks
3737
transformers["services.*.volumes.*"] = transformVolumeMount
3838
transformers["services.*.dns"] = transformStringOrList
39+
transformers["services.*.develop.watch.*.path"] = transformStringOrList
3940
transformers["services.*.devices.*"] = transformDeviceMapping
4041
transformers["services.*.secrets.*"] = transformFileMount
4142
transformers["services.*.configs.*"] = transformFileMount

0 commit comments

Comments
 (0)