mirror of https://github.com/docker/compose.git
				
				
				
			initial support for `sync`
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
		
							parent
							
								
									e63cbfba0e
								
							
						
					
					
						commit
						1640f155e9
					
				
							
								
								
									
										1
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										1
									
								
								go.mod
								
								
								
								
							|  | @ -16,7 +16,6 @@ require ( | |||
| 	github.com/docker/docker v20.10.20+incompatible // replaced; see replace rule for actual version | ||||
| 	github.com/docker/go-connections v0.4.0 | ||||
| 	github.com/docker/go-units v0.5.0 | ||||
| 	github.com/fsnotify/fsnotify v1.6.0 // indirect | ||||
| 	github.com/golang/mock v1.6.0 | ||||
| 	github.com/hashicorp/go-multierror v1.1.1 | ||||
| 	github.com/hashicorp/go-version v1.6.0 | ||||
|  |  | |||
							
								
								
									
										4
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										4
									
								
								go.sum
								
								
								
								
							|  | @ -206,9 +206,8 @@ github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoD | |||
| github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= | ||||
| github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= | ||||
| github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= | ||||
| github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= | ||||
| github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= | ||||
| github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= | ||||
| github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= | ||||
| github.com/fvbommel/sortorder v1.0.2 h1:mV4o8B2hKboCdkJm+a7uX/SIpZob4JzUpc5GGnM45eo= | ||||
| github.com/fvbommel/sortorder v1.0.2/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= | ||||
| 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 | |||
| golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= | ||||
| golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= | ||||
|  |  | |||
|  | @ -551,7 +551,7 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types | |||
| } | ||||
| 
 | ||||
| // getLinks mimics V1 compose/service.py::Service::_get_links()
 | ||||
| func (s composeService) getLinks(ctx context.Context, projectName string, service types.ServiceConfig, number int) ([]string, error) { | ||||
| func (s *composeService) getLinks(ctx context.Context, projectName string, service types.ServiceConfig, number int) ([]string, error) { | ||||
| 	var links []string | ||||
| 	format := func(k, v string) string { | ||||
| 		return fmt.Sprintf("%s:%s", k, v) | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ package compose | |||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
|  | @ -32,56 +32,29 @@ import ( | |||
| ) | ||||
| 
 | ||||
| type DevelopmentConfig struct { | ||||
| 	Sync     map[string]string `json:"sync,omitempty"` | ||||
| 	Excludes []string          `json:"excludes,omitempty"` | ||||
| } | ||||
| 
 | ||||
| const quietPeriod = 2 * time.Second | ||||
| 
 | ||||
| func (s *composeService) Watch(ctx context.Context, project *types.Project, services []string, options api.WatchOptions) error { | ||||
| 	fmt.Fprintln(s.stderr(), "not implemented yet") | ||||
| 	needRebuild := make(chan string) | ||||
| 	needSync := make(chan api.CopyOptions, 5) | ||||
| 
 | ||||
| 	eg, ctx := errgroup.WithContext(ctx) | ||||
| 	needRefresh := make(chan string) | ||||
| 	eg.Go(func() error { | ||||
| 		clock := clockwork.NewRealClock() | ||||
| 		debounce(ctx, clock, quietPeriod, needRefresh, func(services []string) { | ||||
| 			fmt.Fprintf(s.stderr(), "Updating %s after changes were detected\n", strings.Join(services, ", ")) | ||||
| 			imageIds, err := s.build(ctx, project, api.BuildOptions{ | ||||
| 				Services: services, | ||||
| 			}) | ||||
| 			if err != nil { | ||||
| 				fmt.Fprintf(s.stderr(), "Build failed") | ||||
| 			} | ||||
| 			for i, service := range project.Services { | ||||
| 				if id, ok := imageIds[service.Name]; ok { | ||||
| 					service.Image = id | ||||
| 				} | ||||
| 				project.Services[i] = service | ||||
| 			} | ||||
| 
 | ||||
| 			err = s.Up(ctx, project, api.UpOptions{ | ||||
| 				Create: api.CreateOptions{ | ||||
| 					Services: services, | ||||
| 					Inherit:  true, | ||||
| 				}, | ||||
| 				Start: api.StartOptions{ | ||||
| 					Services: services, | ||||
| 					Project:  project, | ||||
| 				}, | ||||
| 			}) | ||||
| 			if err != nil { | ||||
| 				fmt.Fprintf(s.stderr(), "Application failed to start after update") | ||||
| 			} | ||||
| 		}) | ||||
| 		debounce(ctx, clock, quietPeriod, needRebuild, s.makeRebuildFn(ctx, project)) | ||||
| 		return nil | ||||
| 	}) | ||||
| 
 | ||||
| 	eg.Go(s.makeSyncFn(ctx, project, needSync)) | ||||
| 
 | ||||
| 	err := project.WithServices(services, func(service types.ServiceConfig) error { | ||||
| 		var config DevelopmentConfig | ||||
| 		if y, ok := service.Extensions["x-develop"]; ok { | ||||
| 			err := mapstructure.Decode(y, &config) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		config, err := loadDevelopmentConfig(service, project) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if service.Build == nil { | ||||
| 			return errors.New("can't watch a service without a build section") | ||||
|  | @ -98,7 +71,7 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv | |||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		fmt.Println("watching " + context) | ||||
| 		fmt.Fprintf(s.stderr(), "watching %s\n", context) | ||||
| 		err = watcher.Start() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
|  | @ -106,13 +79,32 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv | |||
| 
 | ||||
| 		eg.Go(func() error { | ||||
| 			defer watcher.Close() //nolint:errcheck
 | ||||
| 		WATCH: | ||||
| 			for { | ||||
| 				select { | ||||
| 				case <-ctx.Done(): | ||||
| 					return nil | ||||
| 				case event := <-watcher.Events(): | ||||
| 					log.Println("fs event :", event.Path()) | ||||
| 					needRefresh <- service.Name | ||||
| 					fmt.Fprintf(s.stderr(), "change detected on %s\n", event.Path()) | ||||
| 
 | ||||
| 					for src, dest := range config.Sync { | ||||
| 						path := filepath.Clean(event.Path()) | ||||
| 						src = filepath.Clean(src) | ||||
| 						if watch.IsChild(path, src) { | ||||
| 							rel, err := filepath.Rel(src, path) | ||||
| 							if err != nil { | ||||
| 								return err | ||||
| 							} | ||||
| 							dest = filepath.Join(dest, rel) | ||||
| 							needSync <- api.CopyOptions{ | ||||
| 								Source:      path, | ||||
| 								Destination: fmt.Sprintf("%s:%s", service.Name, dest), | ||||
| 							} | ||||
| 							continue WATCH | ||||
| 						} | ||||
| 					} | ||||
| 
 | ||||
| 					needRebuild <- service.Name | ||||
| 				case err := <-watcher.Errors(): | ||||
| 					return err | ||||
| 				} | ||||
|  | @ -127,6 +119,73 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv | |||
| 	return eg.Wait() | ||||
| } | ||||
| 
 | ||||
| func loadDevelopmentConfig(service types.ServiceConfig, project *types.Project) (DevelopmentConfig, error) { | ||||
| 	var config DevelopmentConfig | ||||
| 	if y, ok := service.Extensions["x-develop"]; ok { | ||||
| 		err := mapstructure.Decode(y, &config) | ||||
| 		if err != nil { | ||||
| 			return DevelopmentConfig{}, err | ||||
| 		} | ||||
| 		for src, dest := range config.Sync { | ||||
| 			if !filepath.IsAbs(src) { | ||||
| 				delete(config.Sync, src) | ||||
| 				src = filepath.Join(project.WorkingDir, src) | ||||
| 				config.Sync[src] = dest | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return config, nil | ||||
| } | ||||
| 
 | ||||
| func (s *composeService) makeRebuildFn(ctx context.Context, project *types.Project) func(services []string) { | ||||
| 	return func(services []string) { | ||||
| 		fmt.Fprintf(s.stderr(), "Updating %s after changes were detected\n", strings.Join(services, ", ")) | ||||
| 		imageIds, err := s.build(ctx, project, api.BuildOptions{ | ||||
| 			Services: services, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			fmt.Fprintf(s.stderr(), "Build failed") | ||||
| 		} | ||||
| 		for i, service := range project.Services { | ||||
| 			if id, ok := imageIds[service.Name]; ok { | ||||
| 				service.Image = id | ||||
| 			} | ||||
| 			project.Services[i] = service | ||||
| 		} | ||||
| 
 | ||||
| 		err = s.Up(ctx, project, api.UpOptions{ | ||||
| 			Create: api.CreateOptions{ | ||||
| 				Services: services, | ||||
| 				Inherit:  true, | ||||
| 			}, | ||||
| 			Start: api.StartOptions{ | ||||
| 				Services: services, | ||||
| 				Project:  project, | ||||
| 			}, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			fmt.Fprintf(s.stderr(), "Application failed to start after update") | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s *composeService) makeSyncFn(ctx context.Context, project *types.Project, needSync chan api.CopyOptions) func() error { | ||||
| 	return func() error { | ||||
| 		for { | ||||
| 			select { | ||||
| 			case <-ctx.Done(): | ||||
| 				return nil | ||||
| 			case opt := <-needSync: | ||||
| 				err := s.Copy(ctx, project.Name, opt) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 				fmt.Fprintf(s.stderr(), "%s updated\n", opt.Source) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func debounce(ctx context.Context, clock clockwork.Clock, delay time.Duration, input chan string, fn func(services []string)) { | ||||
| 	services := utils.Set[string]{} | ||||
| 	t := clock.AfterFunc(delay, func() { | ||||
|  |  | |||
|  | @ -23,7 +23,9 @@ import ( | |||
| 	"path/filepath" | ||||
| 	"runtime" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/pkg/errors" | ||||
| 	"github.com/tilt-dev/fsnotify" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
|  | @ -86,7 +88,7 @@ func NewWatcher(paths []string, ignore PathMatcher) (Notify, error) { | |||
| 	return newWatcher(paths, ignore) | ||||
| } | ||||
| 
 | ||||
| const WindowsBufferSizeEnvVar = "TILT_WATCH_WINDOWS_BUFFER_SIZE" | ||||
| const WindowsBufferSizeEnvVar = "COMPOSE_WATCH_WINDOWS_BUFFER_SIZE" | ||||
| 
 | ||||
| const defaultBufferSize int = 65536 | ||||
| 
 | ||||
|  | @ -102,5 +104,5 @@ func DesiredWindowsBufferSize() int { | |||
| } | ||||
| 
 | ||||
| func IsWindowsShortReadError(err error) bool { | ||||
| 	return runtime.GOOS == "windows" && err != nil && strings.Contains(err.Error(), "short read") | ||||
| 	return runtime.GOOS == "windows" && !errors.Is(err, fsnotify.ErrEventOverflow) | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue