github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/controllers/core/session/reconciler.go (about) 1 package session 2 3 import ( 4 "context" 5 "fmt" 6 7 "sigs.k8s.io/controller-runtime/pkg/builder" 8 9 apierrors "k8s.io/apimachinery/pkg/api/errors" 10 "k8s.io/apimachinery/pkg/types" 11 ctrl "sigs.k8s.io/controller-runtime" 12 ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" 13 "sigs.k8s.io/controller-runtime/pkg/reconcile" 14 15 "github.com/jonboulle/clockwork" 16 17 "github.com/tilt-dev/tilt/internal/controllers/apicmp" 18 "github.com/tilt-dev/tilt/internal/controllers/indexer" 19 "github.com/tilt-dev/tilt/internal/store" 20 "github.com/tilt-dev/tilt/internal/store/sessions" 21 "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1" 22 ) 23 24 // Session reports on current CI/Up state, and determines 25 // when Tilt should exit. 26 // 27 // Reads the Session Spec and updates the Session Status. 28 // 29 // Dispatches an event to the store for handling exits. 30 type Reconciler struct { 31 client ctrlclient.Client 32 st store.RStore 33 requeuer *indexer.Requeuer 34 clock clockwork.Clock 35 } 36 37 var _ reconcile.Reconciler = &Reconciler{} 38 39 func NewReconciler(client ctrlclient.Client, st store.RStore, clock clockwork.Clock) *Reconciler { 40 return &Reconciler{ 41 client: client, 42 st: st, 43 clock: clock, 44 requeuer: indexer.NewRequeuer(), 45 } 46 } 47 48 func (r *Reconciler) Requeue() { 49 r.requeuer.Add(types.NamespacedName{Name: sessions.DefaultSessionName}) 50 } 51 52 func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 53 session := &v1alpha1.Session{} 54 err := r.client.Get(ctx, req.NamespacedName, session) 55 if err != nil && !apierrors.IsNotFound(err) { 56 return ctrl.Result{}, fmt.Errorf("session reconcile: %v", err) 57 } 58 59 if apierrors.IsNotFound(err) || session.ObjectMeta.DeletionTimestamp != nil { 60 // NOTE(nick): This should never happen, and if it does, Tilt should 61 // immediately re-create the session. 62 return ctrl.Result{}, nil 63 } 64 65 return r.maybeUpdateObjectStatus(ctx, session) 66 } 67 68 // maybeUpdateObjectStatus builds the latest status for the Session and persists it. 69 // Should only be called in the main reconciler thread. 70 // 71 // If the status has not changed since the last status update performed (by the 72 // Reconciler), it will be skipped. 73 // 74 // Returns the latest object on success. 75 func (r *Reconciler) maybeUpdateObjectStatus(ctx context.Context, session *v1alpha1.Session) (ctrl.Result, error) { 76 result := ctrl.Result{} 77 status := r.makeLatestStatus(session, &result) 78 if apicmp.DeepEqual(session.Status, status) { 79 // the status hasn't changed - avoid a spurious update 80 return result, nil 81 } 82 83 update := session.DeepCopy() 84 update.Status = status 85 err := r.client.Status().Update(ctx, update) 86 if err != nil { 87 return ctrl.Result{}, err 88 } 89 r.st.Dispatch(sessions.NewSessionStatusUpdateAction(update)) 90 return result, nil 91 } 92 93 func (r *Reconciler) CreateBuilder(mgr ctrl.Manager) (*builder.Builder, error) { 94 b := ctrl.NewControllerManagedBy(mgr). 95 For(&v1alpha1.Session{}). 96 WatchesRawSource(r.requeuer) 97 98 return b, nil 99 }