Skip to content

Commit f9189f8

Browse files
committed
Add multiple paths for rebuild and restart
Signed-off-by: Joana Hrotkó <[email protected]>
1 parent 331db8f commit f9189f8

File tree

6 files changed

+137
-76
lines changed

6 files changed

+137
-76
lines changed

loader/loader_test.go

+8-5
Original file line numberDiff line numberDiff line change
@@ -3073,8 +3073,11 @@ services:
30733073
develop:
30743074
watch:
30753075
# rebuild image and recreate service
3076-
- path: ./backend/src
3077-
action: rebuild
3076+
- action: rebuild
3077+
path:
3078+
- ./backend/src
3079+
- ./backend
3080+
30783081
proxy:
30793082
image: example/proxy
30803083
build: ./proxy
@@ -3094,7 +3097,7 @@ services:
30943097
assert.DeepEqual(t, *frontend.Develop, types.DevelopConfig{
30953098
Watch: []types.Trigger{
30963099
{
3097-
Path: "./webapp/html",
3100+
Path: []string{"./webapp/html"},
30983101
Action: types.WatchActionSync,
30993102
Target: "/var/www",
31003103
Ignore: []string{"node_modules/"},
@@ -3109,7 +3112,7 @@ services:
31093112
assert.DeepEqual(t, *backend.Develop, types.DevelopConfig{
31103113
Watch: []types.Trigger{
31113114
{
3112-
Path: "./backend/src",
3115+
Path: []string{"./backend/src", "./backend"},
31133116
Action: types.WatchActionRebuild,
31143117
},
31153118
},
@@ -3119,7 +3122,7 @@ services:
31193122
assert.DeepEqual(t, *proxy.Develop, types.DevelopConfig{
31203123
Watch: []types.Trigger{
31213124
{
3122-
Path: "./proxy/proxy.conf",
3125+
Path: []string{"./proxy/proxy.conf"},
31233126
Action: types.WatchActionSyncRestart,
31243127
Target: "/etc/nginx/proxy.conf",
31253128
},

loader/validate.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,14 @@ func checkConsistency(project *types.Project) error { //nolint:gocyclo
167167

168168
if s.Develop != nil && s.Develop.Watch != nil {
169169
for _, watch := range s.Develop.Watch {
170-
if watch.Target == "" && watch.Action != types.WatchActionRebuild && watch.Action != types.WatchActionRestart {
171-
return fmt.Errorf("services.%s.develop.watch: target is required for non-rebuild actions: %w", s.Name, errdefs.ErrInvalid)
170+
if watch.Action != types.WatchActionRebuild && watch.Action != types.WatchActionRestart {
171+
if watch.Target == "" {
172+
return fmt.Errorf("services.%s.develop.watch: target is required for %s, %s and %s actions: %w", s.Name, types.WatchActionSync, types.WatchActionSyncExec, types.WatchActionSyncRestart, errdefs.ErrInvalid)
173+
174+
}
175+
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)
177+
}
172178
}
173179
}
174180
}

loader/validate_test.go

+107-64
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package loader
1818

1919
import (
20+
"fmt"
2021
"strings"
2122
"testing"
2223

@@ -291,7 +292,7 @@ func TestValidateWatch(t *testing.T) {
291292
Watch: []types.Trigger{
292293
{
293294
Action: types.WatchActionSync,
294-
Path: "/app",
295+
Path: []string{"/app"},
295296
Target: "/container/app",
296297
},
297298
},
@@ -303,69 +304,6 @@ func TestValidateWatch(t *testing.T) {
303304
assert.NilError(t, err)
304305
})
305306

306-
t.Run("watch missing target for sync action", func(t *testing.T) {
307-
project := types.Project{
308-
Services: types.Services{
309-
"myservice": {
310-
Name: "myservice",
311-
Image: "scratch",
312-
Develop: &types.DevelopConfig{
313-
Watch: []types.Trigger{
314-
{
315-
Action: types.WatchActionSync,
316-
Path: "/app",
317-
},
318-
},
319-
},
320-
},
321-
},
322-
}
323-
err := checkConsistency(&project)
324-
assert.Error(t, err, "services.myservice.develop.watch: target is required for non-rebuild actions: invalid compose project")
325-
})
326-
327-
t.Run("watch missing target for sync+restart action", func(t *testing.T) {
328-
project := types.Project{
329-
Services: types.Services{
330-
"myservice": {
331-
Name: "myservice",
332-
Image: "scratch",
333-
Develop: &types.DevelopConfig{
334-
Watch: []types.Trigger{
335-
{
336-
Action: types.WatchActionSyncRestart,
337-
Path: "/app",
338-
},
339-
},
340-
},
341-
},
342-
},
343-
}
344-
err := checkConsistency(&project)
345-
assert.Error(t, err, "services.myservice.develop.watch: target is required for non-rebuild actions: invalid compose project")
346-
})
347-
348-
t.Run("watch config valid with missing target for rebuild action", func(t *testing.T) {
349-
project := types.Project{
350-
Services: types.Services{
351-
"myservice": {
352-
Name: "myservice",
353-
Image: "scratch",
354-
Develop: &types.DevelopConfig{
355-
Watch: []types.Trigger{
356-
{
357-
Action: types.WatchActionRebuild,
358-
Path: "/app",
359-
},
360-
},
361-
},
362-
},
363-
},
364-
}
365-
err := checkConsistency(&project)
366-
assert.NilError(t, err)
367-
})
368-
369307
t.Run("depends on disabled service", func(t *testing.T) {
370308
project := types.Project{
371309
Services: types.Services{
@@ -406,4 +344,109 @@ func TestValidateWatch(t *testing.T) {
406344
err := checkConsistency(&project)
407345
assert.ErrorContains(t, err, "depends on undefined service")
408346
})
347+
348+
type WatchActionTest struct {
349+
action types.WatchAction
350+
}
351+
tests := []WatchActionTest{
352+
{action: types.WatchActionSync},
353+
{action: types.WatchActionSyncRestart},
354+
{action: types.WatchActionSyncExec},
355+
}
356+
for _, tt := range tests {
357+
t.Run(fmt.Sprintf("watch config is INVALID when missing target for %s action", tt.action), func(t *testing.T) {
358+
project := types.Project{
359+
Services: types.Services{
360+
"myservice": {
361+
Name: "myservice",
362+
Image: "scratch",
363+
Develop: &types.DevelopConfig{
364+
Watch: []types.Trigger{
365+
{
366+
Action: tt.action,
367+
Path: []string{"/app"},
368+
// Missing Target
369+
},
370+
},
371+
},
372+
},
373+
},
374+
}
375+
err := checkConsistency(&project)
376+
assert.Error(t, err, "services.myservice.develop.watch: target is required for sync, sync+exec and sync+restart actions: invalid compose project")
377+
})
378+
379+
t.Run(fmt.Sprintf("watch config is INVALID with one or more paths for %s action", tt.action), func(t *testing.T) {
380+
project := types.Project{
381+
Services: types.Services{
382+
"myservice": {
383+
Name: "myservice",
384+
Image: "scratch",
385+
Develop: &types.DevelopConfig{
386+
Watch: []types.Trigger{
387+
{
388+
Action: tt.action,
389+
Path: []string{"/app", "/app2"}, // should only be one path
390+
Target: "/container/app",
391+
},
392+
},
393+
},
394+
},
395+
},
396+
}
397+
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")
399+
})
400+
}
401+
tests = []WatchActionTest{
402+
{action: types.WatchActionRebuild},
403+
{action: types.WatchActionRestart},
404+
}
405+
for _, tt := range tests {
406+
t.Run(fmt.Sprintf("watch config is VALID with missing target for %s action", tt.action), func(t *testing.T) {
407+
project := types.Project{
408+
Services: types.Services{
409+
"myservice": {
410+
Name: "myservice",
411+
Image: "scratch",
412+
Develop: &types.DevelopConfig{
413+
Watch: []types.Trigger{
414+
{
415+
Action: tt.action,
416+
Path: []string{"/app"},
417+
},
418+
},
419+
},
420+
},
421+
},
422+
}
423+
err := checkConsistency(&project)
424+
assert.NilError(t, err)
425+
})
426+
427+
t.Run(fmt.Sprintf("watch config is VALID with one or more paths for %s action", tt.action), func(t *testing.T) {
428+
project := types.Project{
429+
Services: types.Services{
430+
"myservice": {
431+
Name: "myservice",
432+
Image: "scratch",
433+
Develop: &types.DevelopConfig{
434+
Watch: []types.Trigger{
435+
{
436+
Action: tt.action,
437+
Path: []string{"/app"},
438+
},
439+
{
440+
Action: tt.action,
441+
Path: []string{"/app", "/app2"},
442+
},
443+
},
444+
},
445+
},
446+
},
447+
}
448+
err := checkConsistency(&project)
449+
assert.NilError(t, err)
450+
})
451+
}
409452
}

schema/compose-spec.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@
492492
"required": ["path", "action"],
493493
"properties": {
494494
"ignore": {"type": "array", "items": {"type": "string"}},
495-
"path": {"type": "string"},
495+
"path": {"$ref": "#/definitions/string_or_list"},
496496
"action": {"type": "string", "enum": ["rebuild", "sync", "restart", "sync+restart", "sync+exec"]},
497497
"target": {"type": "string"},
498498
"exec": {"$ref": "#/definitions/service_hook"}

types/develop.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const (
3333
)
3434

3535
type Trigger struct {
36-
Path string `yaml:"path" json:"path"`
36+
Path StringList `yaml:"path" json:"path"`
3737
Action WatchAction `yaml:"action" json:"action"`
3838
Target string `yaml:"target,omitempty" json:"target,omitempty"`
3939
Exec ServiceHook `yaml:"exec,omitempty" json:"exec,omitempty"`

validation/validation.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,18 @@ func checkFileObject(keys ...string) checkerFunc {
8989
}
9090

9191
func checkPath(value any, p tree.Path) error {
92-
v := value.(string)
93-
if v == "" {
94-
return fmt.Errorf("%s: value can't be blank", p)
92+
switch v := value.(type) {
93+
case string:
94+
if v == "" {
95+
return fmt.Errorf("%s: value can't be blank", p)
96+
}
97+
case []interface{}:
98+
for _, el := range v {
99+
e := el.(string)
100+
if e == "" {
101+
return fmt.Errorf("%s: value in paths can't be blank", e)
102+
}
103+
}
95104
}
96105
return nil
97106
}

0 commit comments

Comments
 (0)