github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/controllers/core/dockercomposeservice/project.go (about)

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