Skip to content

Commit 1640f15

Browse files
committed
initial support for sync
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
1 parent e63cbfb commit 1640f15

File tree

5 files changed

+107
-49
lines changed

5 files changed

+107
-49
lines changed

go.mod

-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ require (
1616
github.com/docker/docker v20.10.20+incompatible // replaced; see replace rule for actual version
1717
github.com/docker/go-connections v0.4.0
1818
github.com/docker/go-units v0.5.0
19-
github.com/fsnotify/fsnotify v1.6.0 // indirect
2019
github.com/golang/mock v1.6.0
2120
github.com/hashicorp/go-multierror v1.1.1
2221
github.com/hashicorp/go-version v1.6.0

go.sum

+1-3
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,8 @@ github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoD
206206
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
207207
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
208208
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
209+
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
209210
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
210-
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
211-
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
212211
github.com/fvbommel/sortorder v1.0.2 h1:mV4o8B2hKboCdkJm+a7uX/SIpZob4JzUpc5GGnM45eo=
213212
github.com/fvbommel/sortorder v1.0.2/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
214213
github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
@@ -910,7 +909,6 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
910909
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
911910
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
912911
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
913-
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
914912
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
915913
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
916914
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=

pkg/compose/convergence.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
551551
}
552552

553553
// getLinks mimics V1 compose/service.py::Service::_get_links()
554-
func (s composeService) getLinks(ctx context.Context, projectName string, service types.ServiceConfig, number int) ([]string, error) {
554+
func (s *composeService) getLinks(ctx context.Context, projectName string, service types.ServiceConfig, number int) ([]string, error) {
555555
var links []string
556556
format := func(k, v string) string {
557557
return fmt.Sprintf("%s:%s", k, v)

pkg/compose/watch.go

+100-41
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ package compose
1717
import (
1818
"context"
1919
"fmt"
20-
"log"
20+
"path/filepath"
2121
"strings"
2222
"time"
2323

@@ -32,56 +32,29 @@ import (
3232
)
3333

3434
type DevelopmentConfig struct {
35+
Sync map[string]string `json:"sync,omitempty"`
36+
Excludes []string `json:"excludes,omitempty"`
3537
}
3638

3739
const quietPeriod = 2 * time.Second
3840

3941
func (s *composeService) Watch(ctx context.Context, project *types.Project, services []string, options api.WatchOptions) error {
40-
fmt.Fprintln(s.stderr(), "not implemented yet")
42+
needRebuild := make(chan string)
43+
needSync := make(chan api.CopyOptions, 5)
4144

4245
eg, ctx := errgroup.WithContext(ctx)
43-
needRefresh := make(chan string)
4446
eg.Go(func() error {
4547
clock := clockwork.NewRealClock()
46-
debounce(ctx, clock, quietPeriod, needRefresh, func(services []string) {
47-
fmt.Fprintf(s.stderr(), "Updating %s after changes were detected\n", strings.Join(services, ", "))
48-
imageIds, err := s.build(ctx, project, api.BuildOptions{
49-
Services: services,
50-
})
51-
if err != nil {
52-
fmt.Fprintf(s.stderr(), "Build failed")
53-
}
54-
for i, service := range project.Services {
55-
if id, ok := imageIds[service.Name]; ok {
56-
service.Image = id
57-
}
58-
project.Services[i] = service
59-
}
60-
61-
err = s.Up(ctx, project, api.UpOptions{
62-
Create: api.CreateOptions{
63-
Services: services,
64-
Inherit: true,
65-
},
66-
Start: api.StartOptions{
67-
Services: services,
68-
Project: project,
69-
},
70-
})
71-
if err != nil {
72-
fmt.Fprintf(s.stderr(), "Application failed to start after update")
73-
}
74-
})
48+
debounce(ctx, clock, quietPeriod, needRebuild, s.makeRebuildFn(ctx, project))
7549
return nil
7650
})
7751

52+
eg.Go(s.makeSyncFn(ctx, project, needSync))
53+
7854
err := project.WithServices(services, func(service types.ServiceConfig) error {
79-
var config DevelopmentConfig
80-
if y, ok := service.Extensions["x-develop"]; ok {
81-
err := mapstructure.Decode(y, &config)
82-
if err != nil {
83-
return err
84-
}
55+
config, err := loadDevelopmentConfig(service, project)
56+
if err != nil {
57+
return err
8558
}
8659
if service.Build == nil {
8760
return errors.New("can't watch a service without a build section")
@@ -98,21 +71,40 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv
9871
return err
9972
}
10073

101-
fmt.Println("watching " + context)
74+
fmt.Fprintf(s.stderr(), "watching %s\n", context)
10275
err = watcher.Start()
10376
if err != nil {
10477
return err
10578
}
10679

10780
eg.Go(func() error {
10881
defer watcher.Close() //nolint:errcheck
82+
WATCH:
10983
for {
11084
select {
11185
case <-ctx.Done():
11286
return nil
11387
case event := <-watcher.Events():
114-
log.Println("fs event :", event.Path())
115-
needRefresh <- service.Name
88+
fmt.Fprintf(s.stderr(), "change detected on %s\n", event.Path())
89+
90+
for src, dest := range config.Sync {
91+
path := filepath.Clean(event.Path())
92+
src = filepath.Clean(src)
93+
if watch.IsChild(path, src) {
94+
rel, err := filepath.Rel(src, path)
95+
if err != nil {
96+
return err
97+
}
98+
dest = filepath.Join(dest, rel)
99+
needSync <- api.CopyOptions{
100+
Source: path,
101+
Destination: fmt.Sprintf("%s:%s", service.Name, dest),
102+
}
103+
continue WATCH
104+
}
105+
}
106+
107+
needRebuild <- service.Name
116108
case err := <-watcher.Errors():
117109
return err
118110
}
@@ -127,6 +119,73 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv
127119
return eg.Wait()
128120
}
129121

122+
func loadDevelopmentConfig(service types.ServiceConfig, project *types.Project) (DevelopmentConfig, error) {
123+
var config DevelopmentConfig
124+
if y, ok := service.Extensions["x-develop"]; ok {
125+
err := mapstructure.Decode(y, &config)
126+
if err != nil {
127+
return DevelopmentConfig{}, err
128+
}
129+
for src, dest := range config.Sync {
130+
if !filepath.IsAbs(src) {
131+
delete(config.Sync, src)
132+
src = filepath.Join(project.WorkingDir, src)
133+
config.Sync[src] = dest
134+
}
135+
}
136+
}
137+
return config, nil
138+
}
139+
140+
func (s *composeService) makeRebuildFn(ctx context.Context, project *types.Project) func(services []string) {
141+
return func(services []string) {
142+
fmt.Fprintf(s.stderr(), "Updating %s after changes were detected\n", strings.Join(services, ", "))
143+
imageIds, err := s.build(ctx, project, api.BuildOptions{
144+
Services: services,
145+
})
146+
if err != nil {
147+
fmt.Fprintf(s.stderr(), "Build failed")
148+
}
149+
for i, service := range project.Services {
150+
if id, ok := imageIds[service.Name]; ok {
151+
service.Image = id
152+
}
153+
project.Services[i] = service
154+
}
155+
156+
err = s.Up(ctx, project, api.UpOptions{
157+
Create: api.CreateOptions{
158+
Services: services,
159+
Inherit: true,
160+
},
161+
Start: api.StartOptions{
162+
Services: services,
163+
Project: project,
164+
},
165+
})
166+
if err != nil {
167+
fmt.Fprintf(s.stderr(), "Application failed to start after update")
168+
}
169+
}
170+
}
171+
172+
func (s *composeService) makeSyncFn(ctx context.Context, project *types.Project, needSync chan api.CopyOptions) func() error {
173+
return func() error {
174+
for {
175+
select {
176+
case <-ctx.Done():
177+
return nil
178+
case opt := <-needSync:
179+
err := s.Copy(ctx, project.Name, opt)
180+
if err != nil {
181+
return err
182+
}
183+
fmt.Fprintf(s.stderr(), "%s updated\n", opt.Source)
184+
}
185+
}
186+
}
187+
}
188+
130189
func debounce(ctx context.Context, clock clockwork.Clock, delay time.Duration, input chan string, fn func(services []string)) {
131190
services := utils.Set[string]{}
132191
t := clock.AfterFunc(delay, func() {

pkg/watch/notify.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ import (
2323
"path/filepath"
2424
"runtime"
2525
"strconv"
26-
"strings"
26+
27+
"github.com/pkg/errors"
28+
"github.com/tilt-dev/fsnotify"
2729
)
2830

2931
var (
@@ -86,7 +88,7 @@ func NewWatcher(paths []string, ignore PathMatcher) (Notify, error) {
8688
return newWatcher(paths, ignore)
8789
}
8890

89-
const WindowsBufferSizeEnvVar = "TILT_WATCH_WINDOWS_BUFFER_SIZE"
91+
const WindowsBufferSizeEnvVar = "COMPOSE_WATCH_WINDOWS_BUFFER_SIZE"
9092

9193
const defaultBufferSize int = 65536
9294

@@ -102,5 +104,5 @@ func DesiredWindowsBufferSize() int {
102104
}
103105

104106
func IsWindowsShortReadError(err error) bool {
105-
return runtime.GOOS == "windows" && err != nil && strings.Contains(err.Error(), "short read")
107+
return runtime.GOOS == "windows" && !errors.Is(err, fsnotify.ErrEventOverflow)
106108
}

0 commit comments

Comments
 (0)