github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/engine/uiresource/subscriber.go (about)

     1  package uiresource
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
     8  	"sigs.k8s.io/controller-runtime/pkg/cache"
     9  	ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
    10  
    11  	"github.com/tilt-dev/tilt/internal/controllers/apicmp"
    12  	"github.com/tilt-dev/tilt/internal/hud/webview"
    13  	"github.com/tilt-dev/tilt/internal/store"
    14  	"github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1"
    15  )
    16  
    17  // UIResource objects are created/deleted by the Tiltfile controller.
    18  //
    19  // This subscriber only updates their status.
    20  type Subscriber struct {
    21  	client ctrlclient.Client
    22  }
    23  
    24  func NewSubscriber(client ctrlclient.Client) *Subscriber {
    25  	return &Subscriber{
    26  		client: client,
    27  	}
    28  }
    29  
    30  func (s *Subscriber) currentResources(store store.RStore, disableSources map[string][]v1alpha1.DisableSource) ([]*v1alpha1.UIResource, error) {
    31  	state := store.RLockState()
    32  	defer store.RUnlockState()
    33  	return webview.ToUIResourceList(state, disableSources)
    34  }
    35  
    36  func (s *Subscriber) OnChange(ctx context.Context, st store.RStore, summary store.ChangeSummary) error {
    37  	if summary.IsLogOnly() {
    38  		return nil
    39  	}
    40  
    41  	// Collect a list of all the resources to reconcile and their most recent version.
    42  	storedList := &v1alpha1.UIResourceList{}
    43  	err := s.client.List(ctx, storedList)
    44  
    45  	if err != nil {
    46  		// If the cache hasn't started yet, that's OK.
    47  		// We'll get it on the next OnChange()
    48  		if _, ok := err.(*cache.ErrCacheNotStarted); ok {
    49  			return nil
    50  		}
    51  
    52  		return err
    53  	}
    54  
    55  	storedMap := make(map[string]v1alpha1.UIResource)
    56  	disableSources := make(map[string][]v1alpha1.DisableSource)
    57  	for _, r := range storedList.Items {
    58  		storedMap[r.Name] = r
    59  		disableSources[r.Name] = r.Status.DisableStatus.Sources
    60  	}
    61  
    62  	currentResources, err := s.currentResources(st, disableSources)
    63  	if err != nil {
    64  		st.Dispatch(store.NewErrorAction(fmt.Errorf("cannot convert UIResource: %v", err)))
    65  		return nil
    66  	}
    67  
    68  	errs := []error{}
    69  	for _, r := range currentResources {
    70  		stored, isStored := storedMap[r.Name]
    71  		if !isStored {
    72  			continue
    73  		}
    74  
    75  		reconcileConditions(r.Status.Conditions, stored.Status.Conditions)
    76  
    77  		if !apicmp.DeepEqual(r.Status, stored.Status) {
    78  			// If the current version is different than what's stored, update it.
    79  			update := stored.DeepCopy()
    80  			update.Status = r.Status
    81  			err = s.client.Status().Update(ctx, update)
    82  			if err != nil {
    83  				errs = append(errs, err)
    84  			}
    85  			continue
    86  		}
    87  	}
    88  
    89  	return utilerrors.NewAggregate(errs)
    90  }
    91  
    92  // Update the LastTransitionTime against the currently stored conditions.
    93  func reconcileConditions(conds []v1alpha1.UIResourceCondition, stored []v1alpha1.UIResourceCondition) {
    94  	storedMap := make(map[v1alpha1.UIResourceConditionType]v1alpha1.UIResourceCondition, len(stored))
    95  	for _, c := range stored {
    96  		storedMap[c.Type] = c
    97  	}
    98  
    99  	for i, c := range conds {
   100  		existing, ok := storedMap[c.Type]
   101  		if !ok {
   102  			continue
   103  		}
   104  
   105  		// If the status hasn't changed, fall back to the previous transition time.
   106  		if existing.Status == c.Status {
   107  			c.LastTransitionTime = existing.LastTransitionTime
   108  		}
   109  		conds[i] = c
   110  	}
   111  }
   112  
   113  var _ store.Subscriber = &Subscriber{}