github.com/tilt-dev/tilt@v0.36.0/internal/controllers/core/dockercomposeservice/project.go (about) 1 package dockercomposeservice 2 3 import ( 4 "context" 5 6 "github.com/docker/docker/api/types" 7 8 "github.com/tilt-dev/tilt/internal/controllers/apicmp" 9 "github.com/tilt-dev/tilt/internal/dockercompose" 10 "github.com/tilt-dev/tilt/pkg/logger" 11 ) 12 13 // Sync all the project watches with the dockercompose objects 14 // we're currently tracking. 15 func (r *Reconciler) manageOwnedProjectWatches(ctx context.Context) { 16 r.mu.Lock() 17 defer r.mu.Unlock() 18 19 running := map[string]bool{} 20 for key := range r.projectWatches { 21 running[key] = true 22 } 23 24 owned := map[string]bool{} 25 for _, result := range r.results { 26 hash := result.ProjectHash 27 owned[hash] = true 28 29 if hash != "" && !running[hash] { 30 ctx, cancel := context.WithCancel(ctx) 31 pw := &ProjectWatch{ 32 ctx: ctx, 33 cancel: cancel, 34 project: result.Spec.Project, 35 hash: hash, 36 } 37 r.projectWatches[hash] = pw 38 go r.runProjectWatch(pw) 39 running[hash] = true 40 } 41 } 42 43 for key := range r.projectWatches { 44 if !owned[key] { 45 r.projectWatches[key].cancel() 46 delete(r.projectWatches, key) 47 } 48 } 49 } 50 51 // Stream events from the docker-compose project and 52 // fan them out to each service in the project. 53 func (r *Reconciler) runProjectWatch(pw *ProjectWatch) { 54 defer func() { 55 r.mu.Lock() 56 delete(r.projectWatches, pw.hash) 57 r.mu.Unlock() 58 pw.cancel() 59 }() 60 61 ctx := pw.ctx 62 project := pw.project 63 ch, err := r.dcc.StreamEvents(ctx, project) 64 if err != nil { 65 // TODO(nick): Figure out where this error should be published. 66 return 67 } 68 69 for { 70 select { 71 case evtJson, ok := <-ch: 72 if !ok { 73 return 74 } 75 evt, err := dockercompose.EventFromJsonStr(evtJson) 76 if err != nil { 77 logger.Get(ctx).Debugf("[dcwatch] failed to unmarshal dc event '%s' with err: %v", evtJson, err) 78 continue 79 } 80 81 if evt.Type != dockercompose.TypeContainer { 82 continue 83 } 84 85 containerJSON, err := r.dc.ContainerInspect(ctx, evt.ID) 86 if err != nil { 87 logger.Get(ctx).Debugf("[dcwatch] inspecting container: %v", err) 88 continue 89 } 90 91 if containerJSON.ContainerJSONBase == nil || containerJSON.ContainerJSONBase.State == nil { 92 logger.Get(ctx).Debugf("[dcwatch] inspecting container: no state found") 93 continue 94 } 95 96 r.recordContainerEvent(ctx, evt, containerJSON) 97 98 case <-ctx.Done(): 99 return 100 } 101 } 102 } 103 104 // Record the container event and re-reconcile the dockercompose service. 105 func (r *Reconciler) recordContainerEvent(ctx context.Context, evt dockercompose.Event, containerJSON types.ContainerJSON) { 106 cState := containerJSON.ContainerJSONBase.State 107 state := dockercompose.ToContainerState(cState) 108 healthcheckOutput := dockercompose.ToHealthcheckOutput(cState) 109 110 r.mu.Lock() 111 defer r.mu.Unlock() 112 113 oldOutput := r.healthcheckOutputByServiceName[evt.Service] 114 r.healthcheckOutputByServiceName[evt.Service] = healthcheckOutput 115 if healthcheckOutput != "" && oldOutput != healthcheckOutput { 116 logger.Get(ctx).Warnf("healthcheck: %s", healthcheckOutput) 117 } 118 119 result, ok := r.resultsByServiceName[evt.Service] 120 if !ok { 121 return 122 } 123 124 if apicmp.DeepEqual(state, result.Status.ContainerState) { 125 return 126 } 127 128 // No need to copy because this is a value struct. 129 update := result.Status 130 update.ContainerID = evt.ID 131 update.ContainerState = state 132 result.Status = update 133 r.requeuer.Add(result.Name) 134 }