github.com/grahambrereton-form3/tilt@v0.10.18/internal/engine/configs/configs_controller.go (about)

     1  package configs
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/pkg/errors"
     9  
    10  	"github.com/windmilleng/tilt/internal/docker"
    11  	"github.com/windmilleng/tilt/internal/ospath"
    12  	"github.com/windmilleng/tilt/internal/store"
    13  	"github.com/windmilleng/tilt/internal/tiltfile"
    14  	"github.com/windmilleng/tilt/pkg/logger"
    15  	"github.com/windmilleng/tilt/pkg/model"
    16  )
    17  
    18  type ConfigsController struct {
    19  	disabledForTesting bool
    20  	tfl                tiltfile.TiltfileLoader
    21  	dockerClient       docker.Client
    22  	clock              func() time.Time
    23  }
    24  
    25  func NewConfigsController(tfl tiltfile.TiltfileLoader, dockerClient docker.Client) *ConfigsController {
    26  	return &ConfigsController{
    27  		tfl:          tfl,
    28  		dockerClient: dockerClient,
    29  		clock:        time.Now,
    30  	}
    31  }
    32  
    33  func (cc *ConfigsController) SetTiltfileLoaderForTesting(tfl tiltfile.TiltfileLoader) {
    34  	cc.tfl = tfl
    35  }
    36  
    37  func (cc *ConfigsController) DisableForTesting(disabled bool) {
    38  	cc.disabledForTesting = disabled
    39  }
    40  
    41  // Modeled after BuildController.nextTargetToBuild. Check to see that:
    42  // 1) There's currently no Tiltfile build running,
    43  // 2) There are pending file changes, and
    44  // 3) Those files have changed since the last Tiltfile build
    45  //    (so that we don't keep re-running a failed build)
    46  func (cc *ConfigsController) shouldBuild(state store.EngineState) bool {
    47  	isRunning := !state.TiltfileState.CurrentBuild.StartTime.IsZero()
    48  	if isRunning {
    49  		return false
    50  	}
    51  
    52  	for _, changeTime := range state.PendingConfigFileChanges {
    53  		lastStartTime := state.TiltfileState.LastBuild().StartTime
    54  		if changeTime.After(lastStartTime) {
    55  			return true
    56  		}
    57  	}
    58  	return false
    59  }
    60  
    61  func logTiltfileChanges(ctx context.Context, filesChanged map[string]bool) {
    62  	var filenames []string
    63  	for k := range filesChanged {
    64  		filenames = append(filenames, k)
    65  	}
    66  
    67  	l := logger.Get(ctx)
    68  
    69  	if len(filenames) > 0 {
    70  		p := logger.Green(l).Sprintf("%d changed: ", len(filenames))
    71  		l.Infof("\n%s%v\n", p, ospath.FormatFileChangeList(filenames))
    72  	}
    73  }
    74  
    75  func (cc *ConfigsController) loadTiltfile(ctx context.Context, st store.RStore,
    76  	initManifests []model.ManifestName, filesChanged map[string]bool, tiltfilePath string) {
    77  
    78  	startTime := cc.clock()
    79  	st.Dispatch(ConfigsReloadStartedAction{FilesChanged: filesChanged, StartTime: startTime})
    80  
    81  	actionWriter := NewTiltfileLogWriter(st)
    82  	ctx = logger.WithLogger(ctx, logger.NewLogger(logger.Get(ctx).Level(), actionWriter))
    83  
    84  	state := st.RLockState()
    85  	globalLogLineCountAtExecStart := state.Log.LineCount()
    86  	firstBuild := !state.TiltfileState.StartedFirstBuild()
    87  	if !firstBuild {
    88  		logTiltfileChanges(ctx, filesChanged)
    89  	}
    90  	st.RUnlockState()
    91  
    92  	tlr := cc.tfl.Load(ctx, tiltfilePath, initManifests)
    93  	if tlr.Error == nil && len(tlr.Manifests) == 0 {
    94  		tlr.Error = fmt.Errorf("No resources found. Check out https://docs.tilt.dev/tutorial.html to get started!")
    95  	}
    96  	if tlr.Error != nil {
    97  		logger.Get(ctx).Infof(tlr.Error.Error())
    98  	}
    99  
   100  	if tlr.Orchestrator() != model.OrchestratorUnknown {
   101  		cc.dockerClient.SetOrchestrator(tlr.Orchestrator())
   102  		dockerErr := cc.dockerClient.CheckConnected()
   103  		if tlr.Error == nil && dockerErr != nil {
   104  			tlr.Error = errors.Wrap(dockerErr, "Failed to connect to Docker")
   105  		}
   106  	}
   107  
   108  	st.Dispatch(ConfigsReloadedAction{
   109  		Manifests:                     tlr.Manifests,
   110  		ConfigFiles:                   tlr.ConfigFiles,
   111  		TiltIgnoreContents:            tlr.TiltIgnoreContents,
   112  		FinishTime:                    cc.clock(),
   113  		Err:                           tlr.Error,
   114  		Warnings:                      tlr.Warnings,
   115  		Features:                      tlr.FeatureFlags,
   116  		TeamName:                      tlr.TeamName,
   117  		Secrets:                       tlr.Secrets,
   118  		AnalyticsTiltfileOpt:          tlr.AnalyticsOpt,
   119  		DockerPruneSettings:           tlr.DockerPruneSettings,
   120  		GlobalLogLineCountAtExecStart: globalLogLineCountAtExecStart,
   121  	})
   122  }
   123  
   124  func (cc *ConfigsController) OnChange(ctx context.Context, st store.RStore) {
   125  	if cc.disabledForTesting {
   126  		return
   127  	}
   128  
   129  	state := st.RLockState()
   130  	defer st.RUnlockState()
   131  
   132  	initManifests := state.InitManifests
   133  	if !cc.shouldBuild(state) {
   134  		return
   135  	}
   136  
   137  	filesChanged := make(map[string]bool)
   138  	for k := range state.PendingConfigFileChanges {
   139  		filesChanged[k] = true
   140  	}
   141  
   142  	tiltfilePath, err := state.RelativeTiltfilePath()
   143  	if err != nil {
   144  		st.Dispatch(store.NewErrorAction(err))
   145  		return
   146  	}
   147  
   148  	// Release the state lock and load the tiltfile in a separate goroutine
   149  	go cc.loadTiltfile(ctx, st, initManifests, filesChanged, tiltfilePath)
   150  }