watch.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. /*
  2. Copyright 2020 Docker Compose CLI authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package compose
  14. import (
  15. "context"
  16. "fmt"
  17. "log"
  18. "strings"
  19. "time"
  20. "github.com/compose-spec/compose-go/types"
  21. "github.com/docker/compose/v2/pkg/api"
  22. "github.com/docker/compose/v2/pkg/utils"
  23. "github.com/fsnotify/fsnotify"
  24. "github.com/jonboulle/clockwork"
  25. "github.com/mitchellh/mapstructure"
  26. "github.com/pkg/errors"
  27. "golang.org/x/sync/errgroup"
  28. )
  29. type DevelopmentConfig struct {
  30. }
  31. const quietPeriod = 2 * time.Second
  32. func (s *composeService) Watch(ctx context.Context, project *types.Project, services []string, options api.WatchOptions) error {
  33. fmt.Fprintln(s.stderr(), "not implemented yet")
  34. eg, ctx := errgroup.WithContext(ctx)
  35. needRefresh := make(chan string)
  36. eg.Go(func() error {
  37. clock := clockwork.NewRealClock()
  38. debounce(ctx, clock, quietPeriod, needRefresh, func(services []string) {
  39. fmt.Fprintf(s.stderr(), "Updating %s after changes were detected\n", strings.Join(services, ", "))
  40. imageIds, err := s.build(ctx, project, api.BuildOptions{
  41. Services: services,
  42. })
  43. if err != nil {
  44. fmt.Fprintf(s.stderr(), "Build failed")
  45. }
  46. for i, service := range project.Services {
  47. if id, ok := imageIds[service.Name]; ok {
  48. service.Image = id
  49. }
  50. project.Services[i] = service
  51. }
  52. err = s.Up(ctx, project, api.UpOptions{
  53. Create: api.CreateOptions{
  54. Services: services,
  55. Inherit: true,
  56. },
  57. Start: api.StartOptions{
  58. Services: services,
  59. Project: project,
  60. },
  61. })
  62. if err != nil {
  63. fmt.Fprintf(s.stderr(), "Application failed to start after update")
  64. }
  65. })
  66. return nil
  67. })
  68. err := project.WithServices(services, func(service types.ServiceConfig) error {
  69. var config DevelopmentConfig
  70. if y, ok := service.Extensions["x-develop"]; ok {
  71. err := mapstructure.Decode(y, &config)
  72. if err != nil {
  73. return err
  74. }
  75. }
  76. if service.Build == nil {
  77. return errors.New("can't watch a service without a build section")
  78. }
  79. context := service.Build.Context
  80. watcher, err := fsnotify.NewWatcher()
  81. if err != nil {
  82. return err
  83. }
  84. fmt.Println("watching " + context)
  85. err = watcher.Add(context)
  86. if err != nil {
  87. return err
  88. }
  89. eg.Go(func() error {
  90. defer watcher.Close() //nolint:errcheck
  91. for {
  92. select {
  93. case <-ctx.Done():
  94. return nil
  95. case event := <-watcher.Events:
  96. log.Println("fs event :", event.String())
  97. needRefresh <- service.Name
  98. case err := <-watcher.Errors:
  99. return err
  100. }
  101. }
  102. })
  103. return nil
  104. })
  105. if err != nil {
  106. return err
  107. }
  108. return eg.Wait()
  109. }
  110. func debounce(ctx context.Context, clock clockwork.Clock, delay time.Duration, input chan string, fn func(services []string)) {
  111. services := utils.Set[string]{}
  112. t := clock.AfterFunc(delay, func() {
  113. if len(services) > 0 {
  114. refresh := services.Elements()
  115. services.Clear()
  116. fn(refresh)
  117. }
  118. })
  119. for {
  120. select {
  121. case <-ctx.Done():
  122. return
  123. case service := <-input:
  124. t.Reset(delay)
  125. services.Add(service)
  126. }
  127. }
  128. }