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

     1  package tiltfile
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/tilt-dev/tilt/internal/sliceutils"
     7  	"github.com/tilt-dev/tilt/internal/store"
     8  	"github.com/tilt-dev/tilt/pkg/logger"
     9  	"github.com/tilt-dev/tilt/pkg/model"
    10  )
    11  
    12  const TiltfileBuildSource = "tiltfile"
    13  
    14  func HandleConfigsReloadStarted(
    15  	ctx context.Context,
    16  	state *store.EngineState,
    17  	event ConfigsReloadStartedAction,
    18  ) {
    19  	ms, ok := state.TiltfileStates[event.Name]
    20  	if !ok {
    21  		return
    22  	}
    23  
    24  	status := model.BuildRecord{
    25  		StartTime: event.StartTime,
    26  		Reason:    event.Reason,
    27  		Edits:     event.FilesChanged,
    28  		SpanID:    event.SpanID,
    29  	}
    30  	ms.CurrentBuilds[TiltfileBuildSource] = status
    31  	state.RemoveFromTriggerQueue(event.Name)
    32  	state.StartedTiltfileLoadCount++
    33  }
    34  
    35  // In the original Tilt architecture, the Tiltfile contained
    36  // the whole engine state. Reloading the tiltfile re-created that
    37  // state from scratch.
    38  //
    39  // We've moved towards a more modular architecture, but many of the tilt data
    40  // models aren't modular. For example, if two Tiltfiles set UpdateSettings,
    41  // it's not clear which one "wins" or how their preferences combine.
    42  //
    43  // In the long-term, Tiltfile settings should only take affect in objects created
    44  // by that Tiltfile. (e.g., WatchSettings only affects FileWatches created by
    45  // that Tiltfile.)
    46  //
    47  // In the medium-term, we resolve this in the EngineState in three different ways:
    48  //  1. If a data structure supports merging (like the Manifest map), do a merge.
    49  //  2. If merging fails (like if two Tiltfiles define the same Manifest), log an Error
    50  //     and try to do something reasonable.
    51  //  3. If a data structure does not support merging (like UpdateSettings), only
    52  //     accept that data structure from the "main" tiltfile.
    53  func HandleConfigsReloaded(
    54  	ctx context.Context,
    55  	state *store.EngineState,
    56  	event ConfigsReloadedAction,
    57  ) {
    58  	isMainTiltfile := event.Name == model.MainTiltfileManifestName
    59  
    60  	manifests := event.Manifests
    61  	loadedManifestNames := map[model.ManifestName]bool{}
    62  	for i, m := range manifests {
    63  		loadedManifestNames[m.Name] = true
    64  
    65  		// Properly annotate the manifest with the source tiltfile.
    66  		m.SourceTiltfile = event.Name
    67  		manifests[i] = m
    68  	}
    69  
    70  	ms, ok := state.TiltfileStates[event.Name]
    71  	if !ok {
    72  		return
    73  	}
    74  	b := ms.CurrentBuilds[TiltfileBuildSource]
    75  
    76  	// Remove pending file changes that were consumed by this build.
    77  	for _, status := range ms.BuildStatuses {
    78  		status.ClearPendingChangesBefore(b.StartTime)
    79  	}
    80  
    81  	// Track the new secrets and go back to scrub them.
    82  	newSecrets := model.SecretSet{}
    83  	for k, v := range event.Secrets {
    84  		_, exists := state.Secrets[k]
    85  		if !exists {
    86  			newSecrets[k] = v
    87  		}
    88  	}
    89  
    90  	// Add all secrets, even if we failed.
    91  	state.Secrets.AddAll(event.Secrets)
    92  
    93  	// Retroactively scrub secrets
    94  	state.LogStore.ScrubSecretsStartingAt(newSecrets, event.CheckpointAtExecStart)
    95  
    96  	// Add team id if it exists, even if execution failed.
    97  	if isMainTiltfile && (event.TeamID != "" || event.Err == nil) {
    98  		state.TeamID = event.TeamID
    99  	}
   100  
   101  	// if the ConfigsReloadedAction came from a unit test, there might not be a current build
   102  	if !b.Empty() {
   103  		b.FinishTime = event.FinishTime
   104  		b.Error = event.Err
   105  
   106  		if b.SpanID != "" {
   107  			b.WarningCount = len(state.LogStore.Warnings(b.SpanID))
   108  		}
   109  
   110  		ms.AddCompletedBuild(b)
   111  	}
   112  	delete(ms.CurrentBuilds, TiltfileBuildSource)
   113  	if event.Err != nil {
   114  		// When the Tiltfile had an error, we want to differentiate between two cases:
   115  		//
   116  		// 1) You're running `tilt up` for the first time, and a local() command
   117  		// exited with status code 1.  Partial results (like enabling features)
   118  		// would be helpful.
   119  		//
   120  		// 2) You're running 'tilt up' in the happy state. You edit the Tiltfile,
   121  		// and introduce a syntax error.  You don't want partial results to wipe out
   122  		// your "good" state.
   123  
   124  		// Watch any new config files in the partial state.
   125  		state.TiltfileConfigPaths[event.Name] = sliceutils.AppendWithoutDupes(state.TiltfileConfigPaths[event.Name], event.ConfigFiles...)
   126  
   127  		if isMainTiltfile {
   128  			// Enable any new features in the partial state.
   129  			if len(state.Features) == 0 {
   130  				state.Features = event.Features
   131  			} else {
   132  				for feature, val := range event.Features {
   133  					if val {
   134  						state.Features[feature] = val
   135  					}
   136  				}
   137  			}
   138  		}
   139  		return
   140  	}
   141  
   142  	// Make sure all the new manifests are in the EngineState.
   143  	for _, m := range manifests {
   144  		mt, ok := state.ManifestTargets[m.ManifestName()]
   145  		if ok && mt.Manifest.SourceTiltfile != event.Name {
   146  			logger.Get(ctx).Errorf("Resource defined in two tiltfiles: %s, %s", event.Name, mt.Manifest.SourceTiltfile)
   147  			continue
   148  		}
   149  
   150  		// Create a new manifest if it changed types.
   151  		createNew := !ok ||
   152  			mt.Manifest.IsK8s() != m.IsK8s() ||
   153  			mt.Manifest.IsLocal() != m.IsLocal() ||
   154  			mt.Manifest.IsDC() != m.IsDC()
   155  		if createNew {
   156  			mt = store.NewManifestTarget(m)
   157  		}
   158  
   159  		configFilesThatChanged := ms.LastBuild().Edits
   160  		old := mt.Manifest
   161  		mt.Manifest = m
   162  
   163  		if model.ChangesInvalidateBuild(old, m) {
   164  			// Manifest has changed such that the current build is invalid;
   165  			// ensure we do an image build so that we apply the changes
   166  			ms := mt.State
   167  			ms.BuildStatuses = make(map[model.TargetID]*store.BuildStatus)
   168  			ms.PendingManifestChange = event.FinishTime
   169  			ms.ConfigFilesThatCausedChange = configFilesThatChanged
   170  		}
   171  		state.UpsertManifestTarget(mt)
   172  	}
   173  
   174  	// Go through all the existing manifest targets. If they were from this
   175  	// Tiltfile, but were removed from the latest Tiltfile execution, delete them.
   176  	for _, mt := range state.Targets() {
   177  		m := mt.Manifest
   178  
   179  		if m.SourceTiltfile == event.Name {
   180  			if !loadedManifestNames[m.Name] {
   181  				state.RemoveManifestTarget(m.Name)
   182  			}
   183  			continue
   184  		}
   185  	}
   186  
   187  	state.TiltfileConfigPaths[event.Name] = event.ConfigFiles
   188  
   189  	// Global state that's only configurable from the main manifest.
   190  	if isMainTiltfile {
   191  		state.Features = event.Features
   192  		state.TelemetrySettings = event.TelemetrySettings
   193  		state.VersionSettings = event.VersionSettings
   194  		state.AnalyticsTiltfileOpt = event.AnalyticsTiltfileOpt
   195  		state.UpdateSettings = event.UpdateSettings
   196  		state.DockerPruneSettings = event.DockerPruneSettings
   197  	}
   198  }