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  }