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{}