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

     1  package tiltfile
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"strconv"
     9  	"time"
    10  
    11  	"go.starlark.net/starlark"
    12  
    13  	"github.com/tilt-dev/clusterid"
    14  	"github.com/tilt-dev/tilt/internal/analytics"
    15  	"github.com/tilt-dev/tilt/internal/controllers/apiset"
    16  	"github.com/tilt-dev/tilt/internal/dockercompose"
    17  	"github.com/tilt-dev/tilt/internal/feature"
    18  	"github.com/tilt-dev/tilt/internal/k8s"
    19  	"github.com/tilt-dev/tilt/internal/localexec"
    20  	"github.com/tilt-dev/tilt/internal/ospath"
    21  	"github.com/tilt-dev/tilt/internal/sliceutils"
    22  	tiltfileanalytics "github.com/tilt-dev/tilt/internal/tiltfile/analytics"
    23  	"github.com/tilt-dev/tilt/internal/tiltfile/cisettings"
    24  	"github.com/tilt-dev/tilt/internal/tiltfile/config"
    25  	"github.com/tilt-dev/tilt/internal/tiltfile/dockerprune"
    26  	"github.com/tilt-dev/tilt/internal/tiltfile/hasher"
    27  	"github.com/tilt-dev/tilt/internal/tiltfile/io"
    28  	"github.com/tilt-dev/tilt/internal/tiltfile/k8scontext"
    29  	"github.com/tilt-dev/tilt/internal/tiltfile/secretsettings"
    30  	"github.com/tilt-dev/tilt/internal/tiltfile/starkit"
    31  	"github.com/tilt-dev/tilt/internal/tiltfile/telemetry"
    32  	"github.com/tilt-dev/tilt/internal/tiltfile/tiltextension"
    33  	"github.com/tilt-dev/tilt/internal/tiltfile/updatesettings"
    34  	"github.com/tilt-dev/tilt/internal/tiltfile/v1alpha1"
    35  	"github.com/tilt-dev/tilt/internal/tiltfile/value"
    36  	"github.com/tilt-dev/tilt/internal/tiltfile/version"
    37  	"github.com/tilt-dev/tilt/internal/tiltfile/watch"
    38  	corev1alpha1 "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1"
    39  	"github.com/tilt-dev/tilt/pkg/model"
    40  	wmanalytics "github.com/tilt-dev/wmclient/pkg/analytics"
    41  )
    42  
    43  const FileName = "Tiltfile"
    44  
    45  type TiltfileLoadResult struct {
    46  	Manifests           []model.Manifest
    47  	EnabledManifests    []model.ManifestName
    48  	Tiltignore          model.Dockerignore
    49  	ConfigFiles         []string
    50  	FeatureFlags        map[string]bool
    51  	TeamID              string
    52  	TelemetrySettings   model.TelemetrySettings
    53  	Secrets             model.SecretSet
    54  	Error               error
    55  	DockerPruneSettings model.DockerPruneSettings
    56  	AnalyticsOpt        wmanalytics.Opt
    57  	VersionSettings     model.VersionSettings
    58  	UpdateSettings      model.UpdateSettings
    59  	WatchSettings       model.WatchSettings
    60  	DefaultRegistry     *corev1alpha1.RegistryHosting
    61  	ObjectSet           apiset.ObjectSet
    62  	Hashes              hasher.Hashes
    63  	CISettings          *corev1alpha1.SessionCISpec
    64  
    65  	// For diagnostic purposes only
    66  	BuiltinCalls []starkit.BuiltinCall `json:"-"`
    67  }
    68  
    69  func (r TiltfileLoadResult) HasOrchestrator(orc model.Orchestrator) bool {
    70  	for _, manifest := range r.Manifests {
    71  		if manifest.IsK8s() && orc == model.OrchestratorK8s {
    72  			return true
    73  		} else if manifest.IsDC() && orc == model.OrchestratorDC {
    74  			return true
    75  		}
    76  	}
    77  	return false
    78  }
    79  
    80  func (r TiltfileLoadResult) WithAllManifestsEnabled() TiltfileLoadResult {
    81  	r.EnabledManifests = nil
    82  	for _, m := range r.Manifests {
    83  		r.EnabledManifests = append(r.EnabledManifests, m.Name)
    84  	}
    85  	return r
    86  }
    87  
    88  type TiltfileLoader interface {
    89  	// Load the Tiltfile.
    90  	//
    91  	// By design, Load() always returns a result.
    92  	// We want to be very careful not to treat non-zero exit codes like an error.
    93  	// Because even if the Tiltfile has errors, we might need to watch files
    94  	// or return partial results (like enabled features).
    95  	Load(ctx context.Context, tf *corev1alpha1.Tiltfile, prevResult *TiltfileLoadResult) TiltfileLoadResult
    96  }
    97  
    98  func ProvideTiltfileLoader(
    99  	analytics *analytics.TiltAnalytics,
   100  	k8sContextPlugin k8scontext.Plugin,
   101  	versionPlugin version.Plugin,
   102  	configPlugin *config.Plugin,
   103  	extensionPlugin *tiltextension.Plugin,
   104  	ciSettingsPlugin cisettings.Plugin,
   105  	dcCli dockercompose.DockerComposeClient,
   106  	webHost model.WebHost,
   107  	execer localexec.Execer,
   108  	fDefaults feature.Defaults,
   109  	env clusterid.Product) TiltfileLoader {
   110  	return tiltfileLoader{
   111  		analytics:        analytics,
   112  		k8sContextPlugin: k8sContextPlugin,
   113  		versionPlugin:    versionPlugin,
   114  		configPlugin:     configPlugin,
   115  		extensionPlugin:  extensionPlugin,
   116  		ciSettingsPlugin: ciSettingsPlugin,
   117  		dcCli:            dcCli,
   118  		webHost:          webHost,
   119  		execer:           execer,
   120  		fDefaults:        fDefaults,
   121  		env:              env,
   122  	}
   123  }
   124  
   125  type tiltfileLoader struct {
   126  	analytics *analytics.TiltAnalytics
   127  	dcCli     dockercompose.DockerComposeClient
   128  	webHost   model.WebHost
   129  	execer    localexec.Execer
   130  
   131  	k8sContextPlugin k8scontext.Plugin
   132  	versionPlugin    version.Plugin
   133  	configPlugin     *config.Plugin
   134  	extensionPlugin  *tiltextension.Plugin
   135  	ciSettingsPlugin cisettings.Plugin
   136  	fDefaults        feature.Defaults
   137  	env              clusterid.Product
   138  }
   139  
   140  var _ TiltfileLoader = &tiltfileLoader{}
   141  
   142  // Load loads the Tiltfile in `filename`
   143  func (tfl tiltfileLoader) Load(ctx context.Context, tf *corev1alpha1.Tiltfile, prevResult *TiltfileLoadResult) TiltfileLoadResult {
   144  	start := time.Now()
   145  	filename := tf.Spec.Path
   146  	absFilename, err := ospath.RealAbs(tf.Spec.Path)
   147  	if err != nil {
   148  		if os.IsNotExist(err) {
   149  			return TiltfileLoadResult{
   150  				ConfigFiles: []string{filename},
   151  				Error:       fmt.Errorf("No Tiltfile found at paths '%s'. Check out https://docs.tilt.dev/tutorial.html", filename),
   152  			}
   153  		}
   154  		absFilename, _ = filepath.Abs(filename)
   155  		return TiltfileLoadResult{
   156  			ConfigFiles: []string{absFilename},
   157  			Error:       err,
   158  		}
   159  	}
   160  
   161  	tiltignorePath := watch.TiltignorePath(absFilename)
   162  	tlr := TiltfileLoadResult{
   163  		ConfigFiles: []string{absFilename, tiltignorePath},
   164  	}
   165  
   166  	tiltignore, err := watch.ReadTiltignore(tiltignorePath)
   167  
   168  	// missing tiltignore is fine, but a filesystem error is not
   169  	if err != nil {
   170  		tlr.Error = err
   171  		return tlr
   172  	}
   173  
   174  	tlr.Tiltignore = tiltignore
   175  
   176  	s := newTiltfileState(ctx, tfl.dcCli, tfl.webHost, tfl.execer, tfl.k8sContextPlugin, tfl.versionPlugin,
   177  		tfl.configPlugin, tfl.extensionPlugin, tfl.ciSettingsPlugin, feature.FromDefaults(tfl.fDefaults))
   178  
   179  	manifests, result, err := s.loadManifests(tf)
   180  
   181  	tlr.BuiltinCalls = result.BuiltinCalls
   182  	tlr.DefaultRegistry = s.defaultReg
   183  
   184  	// All data models are loaded with GetState. We ignore the error if the state
   185  	// isn't properly loaded. This is necessary for handling partial Tiltfile
   186  	// execution correctly, where some state is correctly assembled but other
   187  	// state is not (and should be assumed empty).
   188  	ws, _ := watch.GetState(result)
   189  	tlr.WatchSettings = ws
   190  
   191  	// NOTE(maia): if/when add secret settings that affect the engine, add them to tlr here
   192  	ss, _ := secretsettings.GetState(result)
   193  	s.secretSettings = ss
   194  
   195  	ioState, _ := io.GetState(result)
   196  
   197  	tlr.ConfigFiles = append(tlr.ConfigFiles, ioState.Paths...)
   198  	tlr.ConfigFiles = append(tlr.ConfigFiles, s.postExecReadFiles...)
   199  	tlr.ConfigFiles = sliceutils.DedupedAndSorted(tlr.ConfigFiles)
   200  
   201  	dps, _ := dockerprune.GetState(result)
   202  	tlr.DockerPruneSettings = dps
   203  
   204  	aSettings, _ := tiltfileanalytics.GetState(result)
   205  	tlr.AnalyticsOpt = aSettings.Opt
   206  
   207  	tlr.Secrets = s.extractSecrets()
   208  	tlr.FeatureFlags = s.features.ToEnabled()
   209  	tlr.Error = err
   210  	tlr.Manifests = manifests
   211  	tlr.TeamID = s.teamID
   212  
   213  	objectSet, _ := v1alpha1.GetState(result)
   214  	tlr.ObjectSet = objectSet
   215  
   216  	vs, _ := version.GetState(result)
   217  	tlr.VersionSettings = vs
   218  
   219  	telemetrySettings, _ := telemetry.GetState(result)
   220  	tlr.TelemetrySettings = telemetrySettings
   221  
   222  	us, _ := updatesettings.GetState(result)
   223  	tlr.UpdateSettings = us
   224  
   225  	ci, _ := cisettings.GetState(result)
   226  	tlr.CISettings = ci
   227  
   228  	configSettings, _ := config.GetState(result)
   229  	if tlr.Error == nil {
   230  		tlr.EnabledManifests, tlr.Error = configSettings.EnabledResources(tf, manifests)
   231  	}
   232  
   233  	duration := time.Since(start)
   234  	if tlr.Error == nil {
   235  		s.logger.Infof("Successfully loaded Tiltfile (%s)", duration)
   236  	}
   237  	extState, _ := tiltextension.GetState(result)
   238  	hashState, _ := hasher.GetState(result)
   239  
   240  	var prevHashes hasher.Hashes
   241  	if prevResult != nil {
   242  		prevHashes = prevResult.Hashes
   243  	}
   244  	tlr.Hashes = hashState.GetHashes()
   245  
   246  	tfl.reportTiltfileLoaded(s.builtinCallCounts, s.builtinArgCounts, duration,
   247  		extState.ExtsLoaded, prevHashes, tlr.Hashes)
   248  
   249  	if len(aSettings.CustomTagsToReport) > 0 {
   250  		reportCustomTags(tfl.analytics, aSettings.CustomTagsToReport)
   251  	}
   252  
   253  	return tlr
   254  }
   255  
   256  func starlarkValueOrSequenceToSlice(v starlark.Value) []starlark.Value {
   257  	return value.ValueOrSequenceToSlice(v)
   258  }
   259  
   260  func reportCustomTags(a *analytics.TiltAnalytics, tags map[string]string) {
   261  	a.Incr("tiltfile.custom.report", tags)
   262  }
   263  
   264  func (tfl *tiltfileLoader) reportTiltfileLoaded(
   265  	callCounts map[string]int,
   266  	argCounts map[string]map[string]int,
   267  	loadDur time.Duration,
   268  	pluginsLoaded map[string]bool,
   269  	prevHashes hasher.Hashes,
   270  	currHashes hasher.Hashes,
   271  ) {
   272  	tags := make(map[string]string)
   273  
   274  	// env should really be a global tag, but there's a circular dependency
   275  	// between the global tags and env initialization, so we add it manually.
   276  	tags["env"] = k8s.AnalyticsEnv(tfl.env)
   277  	tags["tiltfile.changed"] = strconv.FormatBool(prevHashes.TiltfileSHA256 != "" && prevHashes.TiltfileSHA256 != currHashes.TiltfileSHA256)
   278  	tags["allfiles.changed"] = strconv.FormatBool(prevHashes.AllFilesSHA256 != "" && prevHashes.AllFilesSHA256 != currHashes.AllFilesSHA256)
   279  
   280  	for builtinName, count := range callCounts {
   281  		tags[fmt.Sprintf("tiltfile.invoked.%s", builtinName)] = strconv.Itoa(count)
   282  	}
   283  	for builtinName, counts := range argCounts {
   284  		for argName, count := range counts {
   285  			tags[fmt.Sprintf("tiltfile.invoked.%s.arg.%s", builtinName, argName)] = strconv.Itoa(count)
   286  		}
   287  	}
   288  	tfl.analytics.Incr("tiltfile.loaded", tags)
   289  	tfl.analytics.Timer("tiltfile.load", loadDur, nil)
   290  	for ext := range pluginsLoaded {
   291  		tags := map[string]string{
   292  			"env":      k8s.AnalyticsEnv(tfl.env),
   293  			"ext_name": ext,
   294  		}
   295  		tfl.analytics.Incr("tiltfile.loaded.plugin", tags)
   296  	}
   297  }