github.com/tilt-dev/tilt@v0.36.0/internal/store/buildcontrols/reducers.go (about) 1 package buildcontrols 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/tilt-dev/tilt/internal/container" 9 "github.com/tilt-dev/tilt/internal/dockercompose" 10 "github.com/tilt-dev/tilt/internal/k8s" 11 "github.com/tilt-dev/tilt/internal/store" 12 "github.com/tilt-dev/tilt/internal/store/dockercomposeservices" 13 "github.com/tilt-dev/tilt/internal/timecmp" 14 "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1" 15 "github.com/tilt-dev/tilt/pkg/logger" 16 "github.com/tilt-dev/tilt/pkg/model" 17 ) 18 19 const BuildControlSource = "buildcontrol" 20 21 func HandleBuildStarted(ctx context.Context, state *store.EngineState, action BuildStartedAction) { 22 if action.Source == BuildControlSource { 23 state.BuildControllerStartCount++ 24 } 25 26 mn := action.ManifestName 27 manifest, ok := state.Manifest(mn) 28 if !ok { 29 return 30 } 31 32 ms, ok := state.ManifestState(mn) 33 if !ok { 34 return 35 } 36 37 bs := model.BuildRecord{ 38 Edits: append([]string{}, action.FilesChanged...), 39 StartTime: action.StartTime, 40 Reason: action.Reason, 41 SpanID: action.SpanID, 42 } 43 ms.ConfigFilesThatCausedChange = []string{} 44 ms.CurrentBuilds[action.Source] = bs 45 46 if ms.IsK8s() { 47 krs := ms.K8sRuntimeState() 48 podIDSet := map[k8s.PodID]bool{} 49 for _, pod := range krs.GetPods() { 50 podIDSet[k8s.PodID(pod.Name)] = true 51 krs.UpdateStartTime[k8s.PodID(pod.Name)] = action.StartTime 52 } 53 // remove stale pods 54 for podID := range krs.UpdateStartTime { 55 if !podIDSet[podID] { 56 delete(krs.UpdateStartTime, podID) 57 } 58 } 59 } else if manifest.IsDC() { 60 // Attach the SpanID and initialize the runtime state if we haven't yet. 61 state, _ := ms.RuntimeState.(dockercompose.State) 62 state = state.WithSpanID(dockercomposeservices.SpanIDForDCService(mn)) 63 ms.RuntimeState = state 64 } 65 66 state.RemoveFromTriggerQueue(mn) 67 state.CurrentBuildSet[mn] = true 68 } 69 70 // When a Manifest build finishes, update the BuildStatus for all applicable 71 // targets in the engine state. 72 func handleBuildResults(engineState *store.EngineState, 73 mt *store.ManifestTarget, br model.BuildRecord, results store.BuildResultSet) { 74 isBuildSuccess := br.Error == nil 75 76 ms := mt.State 77 mn := mt.Manifest.Name 78 for id, result := range results { 79 bs, ok := ms.BuildStatus(id) 80 if ok { 81 bs.LastResult = result 82 } 83 } 84 85 // Remove pending file changes that were consumed by this build. 86 for _, status := range ms.BuildStatuses { 87 status.ConsumeChangesBefore(br.StartTime) 88 } 89 90 if isBuildSuccess { 91 ms.LastSuccessfulDeployTime = br.FinishTime 92 } 93 94 // Update build statuses for duplicated image targets in other manifests. 95 // This ensures that those images targets aren't redundantly rebuilt. 96 for _, currentMT := range engineState.TargetsBesides(mn) { 97 // We only want to update image targets for Manifests that are already queued 98 // for rebuild and not currently building. This has two benefits: 99 // 100 // 1) If there's a bug in Tilt's image caching (e.g., 101 // https://github.com/tilt-dev/tilt/pull/3542), this prevents infinite 102 // builds. 103 // 104 // 2) If the current manifest build was kicked off by a trigger, we don't 105 // want to queue manifests with the same image. 106 if currentMT.NextBuildReason() == model.BuildReasonNone || 107 engineState.IsBuilding(currentMT.Manifest.Name) { 108 continue 109 } 110 111 currentMS := currentMT.State 112 idSet := currentMT.Manifest.TargetIDSet() 113 updatedIDSet := make(map[model.TargetID]bool) 114 115 for id, result := range results { 116 _, ok := idSet[id] 117 if !ok { 118 continue 119 } 120 121 // We can only reuse image update, not live-updates or other kinds of 122 // deploys. 123 _, isImageResult := result.(store.ImageBuildResult) 124 if !isImageResult { 125 continue 126 } 127 128 currentStatus, ok := currentMS.BuildStatus(id) 129 if ok { 130 currentStatus.LastResult = result 131 currentStatus.ConsumeChangesBefore(br.StartTime) 132 } 133 updatedIDSet[id] = true 134 } 135 136 if len(updatedIDSet) == 0 { 137 continue 138 } 139 140 // Suppose we built manifestA, which contains imageA depending on imageCommon. 141 // 142 // We also have manifestB, which contains imageB depending on imageCommon. 143 // 144 // We need to mark imageB as dirty, because it was not built in the manifestA 145 // build but its dependency was built. 146 // 147 // Note that this logic also applies to deploy targets depending on image 148 // targets. If we built manifestA, which contains imageX and deploy target 149 // k8sA, and manifestB contains imageX and deploy target k8sB, we need to mark 150 // target k8sB as dirty so that Tilt actually deploys the changes to imageX. 151 rDepsMap := currentMT.Manifest.ReverseDependencyIDs() 152 for updatedID := range updatedIDSet { 153 154 // Go through each target depending on an image we just built. 155 for _, rDepID := range rDepsMap[updatedID] { 156 157 // If that target was also built, it's up-to-date. 158 if updatedIDSet[rDepID] { 159 continue 160 } 161 162 // Otherwise, we need to mark it for rebuild to pick up the new image. 163 bs, ok := currentMS.BuildStatus(rDepID) 164 if ok { 165 bs.DependencyChanges[updatedID] = br.StartTime 166 } 167 } 168 } 169 } 170 } 171 172 func HandleBuildCompleted(ctx context.Context, engineState *store.EngineState, cb BuildCompleteAction) { 173 mn := cb.ManifestName 174 defer func() { 175 if !engineState.IsBuilding(mn) { 176 delete(engineState.CurrentBuildSet, mn) 177 } 178 }() 179 180 engineState.CompletedBuildCount++ 181 182 mt, ok := engineState.ManifestTargets[mn] 183 if !ok { 184 return 185 } 186 187 err := cb.Error 188 if err != nil { 189 s := fmt.Sprintf("Build Failed: %v", err) 190 191 engineState.LogStore.Append( 192 store.NewLogAction(mt.Manifest.Name, cb.SpanID, logger.ErrorLvl, nil, []byte(s)), 193 engineState.Secrets) 194 } 195 196 ms := mt.State 197 bs := ms.CurrentBuilds[cb.Source] 198 bs.Error = err 199 bs.FinishTime = cb.FinishTime 200 bs.BuildTypes = cb.Result.BuildTypes() 201 if bs.SpanID != "" { 202 bs.WarningCount = len(engineState.LogStore.Warnings(bs.SpanID)) 203 } 204 205 ms.AddCompletedBuild(bs) 206 207 delete(ms.CurrentBuilds, cb.Source) 208 209 handleBuildResults(engineState, mt, bs, cb.Result) 210 211 if !ms.PendingManifestChange.IsZero() && 212 timecmp.BeforeOrEqual(ms.PendingManifestChange, bs.StartTime) { 213 ms.PendingManifestChange = time.Time{} 214 } 215 216 if err != nil { 217 if IsFatalError(err) { 218 engineState.FatalError = err 219 return 220 } 221 } 222 223 manifest := mt.Manifest 224 if manifest.IsK8s() { 225 state := ms.K8sRuntimeState() 226 227 applyFilter := cb.Result.ApplyFilter() 228 if applyFilter != nil && len(applyFilter.DeployedRefs) > 0 { 229 state.ApplyFilter = applyFilter 230 } 231 232 if err == nil { 233 state.HasEverDeployedSuccessfully = true 234 } 235 236 ms.RuntimeState = state 237 } 238 239 if mt.Manifest.IsDC() { 240 state, _ := ms.RuntimeState.(dockercompose.State) 241 242 result := cb.Result[mt.Manifest.DockerComposeTarget().ID()] 243 dcResult, _ := result.(store.DockerComposeBuildResult) 244 cid := dcResult.Status.ContainerID 245 if cid != "" { 246 state = state.WithContainerID(container.ID(cid)) 247 } 248 249 cState := dcResult.Status.ContainerState 250 if cState != nil { 251 state = state.WithContainerState(*cState) 252 state = state.WithPorts(dcResult.Status.PortBindings) 253 } 254 255 ms.RuntimeState = state 256 } 257 258 if mt.Manifest.IsLocal() { 259 lrs := ms.LocalRuntimeState() 260 if err == nil { 261 lt := mt.Manifest.LocalTarget() 262 if lt.ReadinessProbe == nil { 263 // only update the succeeded time if there's no readiness probe 264 lrs.LastReadyOrSucceededTime = time.Now() 265 } 266 if lt.ServeCmd.Empty() { 267 // local resources without a serve command are jobs that run and 268 // terminate; so there's no real runtime status 269 lrs.Status = v1alpha1.RuntimeStatusNotApplicable 270 } 271 } 272 ms.RuntimeState = lrs 273 } 274 }