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  }