github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/controllers/core/extension/tiltfiles.go (about) 1 package extension 2 3 import ( 4 "context" 5 "fmt" 6 7 apierrors "k8s.io/apimachinery/pkg/api/errors" 8 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 "k8s.io/apimachinery/pkg/types" 10 errorutil "k8s.io/apimachinery/pkg/util/errors" 11 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 12 13 "github.com/tilt-dev/tilt/internal/controllers/apicmp" 14 "github.com/tilt-dev/tilt/internal/controllers/indexer" 15 "github.com/tilt-dev/tilt/pkg/apis" 16 "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1" 17 "github.com/tilt-dev/tilt/pkg/model" 18 ) 19 20 var ( 21 apiGVStr = v1alpha1.SchemeGroupVersion.String() 22 apiKind = "Extension" 23 apiType = metav1.TypeMeta{Kind: apiKind, APIVersion: apiGVStr} 24 ) 25 26 // Reconcile the Tiltfile owned by this Extension. 27 // 28 // TODO(nick): This is almost exactly the same as the code for managing PortForwards 29 // from KubernetesDiscovery. I feel like we're missing some abstraction here. 30 func (r *Reconciler) manageOwnedTiltfile(ctx context.Context, nn types.NamespacedName, owner *v1alpha1.Extension) error { 31 var childList v1alpha1.TiltfileList 32 err := indexer.ListOwnedBy(ctx, r.ctrlClient, &childList, nn, apiType) 33 if err != nil { 34 return fmt.Errorf("failed to fetch managed Tiltfile objects for Extension %s: %v", 35 owner.Name, err) 36 } 37 38 child, err := r.toDesiredTiltfile(owner) 39 if err != nil { 40 return fmt.Errorf("creating tiltfile: %v", err) 41 } 42 43 // Delete all the Tiltfiles that don't match this one. 44 errs := []error{} 45 foundDesired := false 46 for _, existingChild := range childList.Items { 47 matches := child != nil && existingChild.Name == child.Name 48 if matches { 49 foundDesired = true 50 51 if apicmp.DeepEqual(child.Spec, existingChild.Spec) { 52 continue 53 } 54 55 updated := existingChild.DeepCopy() 56 updated.Spec = child.Spec 57 err := r.ctrlClient.Update(ctx, updated) 58 if err != nil && !apierrors.IsNotFound(err) { 59 errs = append(errs, fmt.Errorf("updating tiltfile %s: %v", existingChild.Name, err)) 60 } 61 continue 62 } 63 64 // If this does not match the desired child, this child needs to be garbage collected. 65 deletedChild := existingChild.DeepCopy() 66 err := r.ctrlClient.Delete(ctx, deletedChild) 67 if err != nil && !apierrors.IsNotFound(err) { 68 errs = append(errs, fmt.Errorf("deleting tiltfile %s: %v", existingChild.Name, err)) 69 } 70 } 71 72 if !foundDesired && child != nil { 73 err := r.ctrlClient.Create(ctx, child) 74 if err != nil && !apierrors.IsAlreadyExists(err) { 75 errs = append(errs, fmt.Errorf("creating tiltfile %s: %v", child.Name, err)) 76 } 77 } 78 79 return errorutil.NewAggregate(errs) 80 } 81 82 // Construct the desired tiltfile. May be nil. 83 func (r *Reconciler) toDesiredTiltfile(owner *v1alpha1.Extension) (*v1alpha1.Tiltfile, error) { 84 if owner == nil { 85 return nil, nil 86 } 87 88 // Extensions can be loaded as an independent resource, or can be loaded 89 // for their symbols into a tiltfile. If they're loaded for their symbols, 90 // we mark them as managedby the tiltfile loader, and don't bother loading 91 // the tiltfile independently. 92 if owner.Annotations[v1alpha1.AnnotationManagedBy] != "" { 93 return nil, nil 94 } 95 96 path := owner.Status.Path 97 if path == "" { 98 return nil, nil 99 } 100 101 // Each extensions resources get their own group. 102 // We prefix it with 'extension' so that all extensions get put together. 103 // TODO(nick): Let the user choose the label when they register the extension. 104 label := apis.SanitizeLabel(fmt.Sprintf("extension.%s", owner.Name)) 105 fwName := apis.SanitizeName(fmt.Sprintf("%s:%s", model.TargetTypeConfigs, owner.Name)) 106 child := &v1alpha1.Tiltfile{ 107 ObjectMeta: metav1.ObjectMeta{ 108 Name: owner.Name, 109 Annotations: map[string]string{ 110 v1alpha1.AnnotationManifest: owner.Name, 111 }, 112 }, 113 Spec: v1alpha1.TiltfileSpec{ 114 Path: path, 115 Labels: map[string]string{ 116 label: label, 117 }, 118 Args: owner.Spec.Args, 119 RestartOn: &v1alpha1.RestartOnSpec{ 120 // It's OK if this filewatch doesn't exist yet. 121 // (Tiltfiles are weird in that the Tiltfile reconciler manages the filewatch.) 122 FileWatches: []string{fwName}, 123 }, 124 }, 125 } 126 err := controllerutil.SetControllerReference(owner, child, r.ctrlClient.Scheme()) 127 if err != nil { 128 return nil, err 129 } 130 return child, nil 131 }