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 }