Skip to content

Commit 4aacd02

Browse files
committed
Add multiple paths for rebuild and restart
Signed-off-by: Joana Hrotkó <[email protected]>
1 parent 84b7d5a commit 4aacd02

File tree

6 files changed

+137
-77
lines changed

6 files changed

+137
-77
lines changed

loader/loader_test.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3119,8 +3119,11 @@ services:
31193119
develop:
31203120
watch:
31213121
# rebuild image and recreate service
3122-
- path: ./backend/src
3123-
action: rebuild
3122+
- action: rebuild
3123+
path:
3124+
- ./backend/src
3125+
- ./backend
3126+
31243127
proxy:
31253128
image: example/proxy
31263129
build: ./proxy
@@ -3140,7 +3143,7 @@ services:
31403143
assert.DeepEqual(t, *frontend.Develop, types.DevelopConfig{
31413144
Watch: []types.Trigger{
31423145
{
3143-
Path: "./webapp/html",
3146+
Path: []string{"./webapp/html"},
31443147
Action: types.WatchActionSync,
31453148
Target: "/var/www",
31463149
Ignore: []string{"node_modules/"},
@@ -3155,7 +3158,7 @@ services:
31553158
assert.DeepEqual(t, *backend.Develop, types.DevelopConfig{
31563159
Watch: []types.Trigger{
31573160
{
3158-
Path: "./backend/src",
3161+
Path: []string{"./backend/src", "./backend"},
31593162
Action: types.WatchActionRebuild,
31603163
},
31613164
},
@@ -3165,7 +3168,7 @@ services:
31653168
assert.DeepEqual(t, *proxy.Develop, types.DevelopConfig{
31663169
Watch: []types.Trigger{
31673170
{
3168-
Path: "./proxy/proxy.conf",
3171+
Path: []string{"./proxy/proxy.conf"},
31693172
Action: types.WatchActionSyncRestart,
31703173
Target: "/etc/nginx/proxy.conf",
31713174
},

loader/validate.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,16 @@ func checkConsistency(project *types.Project) error {
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
}
174-
175180
}
176181
}
177182

loader/validate_test.go

Lines changed: 107 additions & 64 deletions
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
},
@@ -304,69 +305,6 @@ func TestValidateWatch(t *testing.T) {
304305

305306
})
306307

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

schema/compose-spec.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@
491491
"required": ["path", "action"],
492492
"properties": {
493493
"ignore": {"type": "array", "items": {"type": "string"}},
494-
"path": {"type": "string"},
494+
"path": {"$ref": "#/definitions/string_or_list"},
495495
"action": {"type": "string", "enum": ["rebuild", "sync", "restart", "sync+restart", "sync+exec"]},
496496
"target": {"type": "string"},
497497
"exec": {"$ref": "#/definitions/service_hook"}

types/develop.go

Lines changed: 1 addition & 1 deletion
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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,18 @@ func checkFileObject(keys ...string) checkerFunc {
9090
}
9191

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

0 commit comments

Comments
 (0)