@@ -17,7 +17,7 @@ package compose
17
17
import (
18
18
"context"
19
19
"fmt"
20
- "log "
20
+ "path/filepath "
21
21
"strings"
22
22
"time"
23
23
@@ -32,56 +32,29 @@ import (
32
32
)
33
33
34
34
type DevelopmentConfig struct {
35
+ Sync map [string ]string `json:"sync,omitempty"`
36
+ Excludes []string `json:"excludes,omitempty"`
35
37
}
36
38
37
39
const quietPeriod = 2 * time .Second
38
40
39
41
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 )
41
44
42
45
eg , ctx := errgroup .WithContext (ctx )
43
- needRefresh := make (chan string )
44
46
eg .Go (func () error {
45
47
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 ))
75
49
return nil
76
50
})
77
51
52
+ eg .Go (s .makeSyncFn (ctx , project , needSync ))
53
+
78
54
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
85
58
}
86
59
if service .Build == nil {
87
60
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
98
71
return err
99
72
}
100
73
101
- fmt .Println ( "watching " + context )
74
+ fmt .Fprintf ( s . stderr (), "watching %s \n " , context )
102
75
err = watcher .Start ()
103
76
if err != nil {
104
77
return err
105
78
}
106
79
107
80
eg .Go (func () error {
108
81
defer watcher .Close () //nolint:errcheck
82
+ WATCH:
109
83
for {
110
84
select {
111
85
case <- ctx .Done ():
112
86
return nil
113
87
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
116
108
case err := <- watcher .Errors ():
117
109
return err
118
110
}
@@ -127,6 +119,73 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv
127
119
return eg .Wait ()
128
120
}
129
121
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
+
130
189
func debounce (ctx context.Context , clock clockwork.Clock , delay time.Duration , input chan string , fn func (services []string )) {
131
190
services := utils.Set [string ]{}
132
191
t := clock .AfterFunc (delay , func () {
0 commit comments