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

     1  package tiltfile
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"strconv"
    10  	"time"
    11  
    12  	"go.starlark.net/resolve"
    13  	"go.starlark.net/starlark"
    14  
    15  	wmanalytics "github.com/windmilleng/wmclient/pkg/analytics"
    16  
    17  	"github.com/windmilleng/tilt/internal/analytics"
    18  	"github.com/windmilleng/tilt/internal/dockercompose"
    19  	"github.com/windmilleng/tilt/internal/feature"
    20  	"github.com/windmilleng/tilt/internal/k8s"
    21  	"github.com/windmilleng/tilt/internal/ospath"
    22  	"github.com/windmilleng/tilt/internal/sliceutils"
    23  	tiltfileanalytics "github.com/windmilleng/tilt/internal/tiltfile/analytics"
    24  	"github.com/windmilleng/tilt/internal/tiltfile/dockerprune"
    25  	"github.com/windmilleng/tilt/internal/tiltfile/io"
    26  	"github.com/windmilleng/tilt/internal/tiltfile/k8scontext"
    27  	"github.com/windmilleng/tilt/internal/tiltfile/value"
    28  	"github.com/windmilleng/tilt/pkg/model"
    29  )
    30  
    31  const FileName = "Tiltfile"
    32  const TiltIgnoreFileName = ".tiltignore"
    33  
    34  func init() {
    35  	resolve.AllowLambda = true
    36  	resolve.AllowNestedDef = true
    37  	resolve.AllowGlobalReassign = true
    38  }
    39  
    40  type TiltfileLoadResult struct {
    41  	Manifests           []model.Manifest
    42  	ConfigFiles         []string
    43  	Warnings            []string
    44  	TiltIgnoreContents  string
    45  	FeatureFlags        map[string]bool
    46  	TeamName            string
    47  	Secrets             model.SecretSet
    48  	Error               error
    49  	DockerPruneSettings model.DockerPruneSettings
    50  	AnalyticsOpt        wmanalytics.Opt
    51  }
    52  
    53  func (r TiltfileLoadResult) Orchestrator() model.Orchestrator {
    54  	for _, manifest := range r.Manifests {
    55  		if manifest.IsK8s() {
    56  			return model.OrchestratorK8s
    57  		} else if manifest.IsDC() {
    58  			return model.OrchestratorDC
    59  		}
    60  	}
    61  	return model.OrchestratorUnknown
    62  }
    63  
    64  type TiltfileLoader interface {
    65  	// Load the Tiltfile.
    66  	//
    67  	// By design, Load() always returns a result.
    68  	// We want to be very careful not to treat non-zero exit codes like an error.
    69  	// Because even if the Tiltfile has errors, we might need to watch files
    70  	// or return partial results (like enabled features).
    71  	Load(ctx context.Context, filename string, requestedManifests []model.ManifestName) TiltfileLoadResult
    72  }
    73  
    74  type FakeTiltfileLoader struct {
    75  	Result TiltfileLoadResult
    76  }
    77  
    78  var _ TiltfileLoader = &FakeTiltfileLoader{}
    79  
    80  func NewFakeTiltfileLoader() *FakeTiltfileLoader {
    81  	return &FakeTiltfileLoader{}
    82  }
    83  
    84  func (tfl *FakeTiltfileLoader) Load(ctx context.Context, filename string, requestedManifests []model.ManifestName) TiltfileLoadResult {
    85  	return tfl.Result
    86  }
    87  
    88  func ProvideTiltfileLoader(
    89  	analytics *analytics.TiltAnalytics,
    90  	kCli k8s.Client,
    91  	k8sContextExt k8scontext.Extension,
    92  	dcCli dockercompose.DockerComposeClient,
    93  	fDefaults feature.Defaults) TiltfileLoader {
    94  	return tiltfileLoader{
    95  		analytics:     analytics,
    96  		kCli:          kCli,
    97  		k8sContextExt: k8sContextExt,
    98  		dcCli:         dcCli,
    99  		fDefaults:     fDefaults,
   100  	}
   101  }
   102  
   103  type tiltfileLoader struct {
   104  	analytics *analytics.TiltAnalytics
   105  	kCli      k8s.Client
   106  	dcCli     dockercompose.DockerComposeClient
   107  
   108  	k8sContextExt k8scontext.Extension
   109  	fDefaults     feature.Defaults
   110  }
   111  
   112  var _ TiltfileLoader = &tiltfileLoader{}
   113  
   114  func printWarnings(s *tiltfileState) {
   115  	for _, w := range s.warnings {
   116  		s.logger.Infof("WARNING: %s\n", w)
   117  	}
   118  }
   119  
   120  // Load loads the Tiltfile in `filename`
   121  func (tfl tiltfileLoader) Load(ctx context.Context, filename string, requestedManifests []model.ManifestName) TiltfileLoadResult {
   122  	start := time.Now()
   123  	absFilename, err := ospath.RealAbs(filename)
   124  	if err != nil {
   125  		if os.IsNotExist(err) {
   126  			return TiltfileLoadResult{
   127  				ConfigFiles: []string{filename},
   128  				Error:       fmt.Errorf("No Tiltfile found at paths '%s'. Check out https://docs.tilt.dev/tutorial.html", filename),
   129  			}
   130  		}
   131  		absFilename, _ = filepath.Abs(filename)
   132  		return TiltfileLoadResult{
   133  			ConfigFiles: []string{absFilename},
   134  			Error:       err,
   135  		}
   136  	}
   137  
   138  	tiltIgnorePath := tiltIgnorePath(absFilename)
   139  	tlr := TiltfileLoadResult{
   140  		ConfigFiles: []string{absFilename, tiltIgnorePath},
   141  	}
   142  
   143  	tiltIgnoreContents, err := ioutil.ReadFile(tiltIgnorePath)
   144  
   145  	// missing tiltignore is fine, but a filesystem error is not
   146  	if err != nil && !os.IsNotExist(err) {
   147  		tlr.Error = err
   148  		return tlr
   149  	}
   150  
   151  	tlr.TiltIgnoreContents = string(tiltIgnoreContents)
   152  
   153  	privateRegistry := tfl.kCli.PrivateRegistry(ctx)
   154  	s := newTiltfileState(ctx, tfl.dcCli, tfl.k8sContextExt, privateRegistry, feature.FromDefaults(tfl.fDefaults))
   155  
   156  	manifests, result, err := s.loadManifests(absFilename, requestedManifests)
   157  
   158  	ioState, _ := io.GetState(result)
   159  	tlr.ConfigFiles = sliceutils.AppendWithoutDupes(ioState.Files, s.postExecReadFiles...)
   160  
   161  	dps, _ := dockerprune.GetState(result)
   162  	tlr.DockerPruneSettings = dps
   163  
   164  	aSettings, _ := tiltfileanalytics.GetState(result)
   165  	tlr.AnalyticsOpt = aSettings.Opt
   166  
   167  	tlr.Secrets = s.extractSecrets()
   168  	tlr.Warnings = s.warnings
   169  	tlr.FeatureFlags = s.features.ToEnabled()
   170  	tlr.Error = err
   171  	tlr.Manifests = manifests
   172  	tlr.TeamName = s.teamName
   173  
   174  	printWarnings(s)
   175  
   176  	duration := time.Since(start)
   177  	s.logger.Infof("Successfully loaded Tiltfile (%s)", duration)
   178  	tfl.reportTiltfileLoaded(s.builtinCallCounts, s.builtinArgCounts, duration)
   179  
   180  	return tlr
   181  }
   182  
   183  // .tiltignore sits next to Tiltfile
   184  func tiltIgnorePath(tiltfilePath string) string {
   185  	return filepath.Join(filepath.Dir(tiltfilePath), TiltIgnoreFileName)
   186  }
   187  
   188  func starlarkValueOrSequenceToSlice(v starlark.Value) []starlark.Value {
   189  	return value.ValueOrSequenceToSlice(v)
   190  }
   191  
   192  func (tfl *tiltfileLoader) reportTiltfileLoaded(callCounts map[string]int,
   193  	argCounts map[string]map[string]int, loadDur time.Duration) {
   194  	tags := make(map[string]string)
   195  	for builtinName, count := range callCounts {
   196  		tags[fmt.Sprintf("tiltfile.invoked.%s", builtinName)] = strconv.Itoa(count)
   197  	}
   198  	for builtinName, counts := range argCounts {
   199  		for argName, count := range counts {
   200  			tags[fmt.Sprintf("tiltfile.invoked.%s.arg.%s", builtinName, argName)] = strconv.Itoa(count)
   201  		}
   202  	}
   203  	tfl.analytics.Incr("tiltfile.loaded", tags)
   204  	tfl.analytics.Timer("tiltfile.load", loadDur, nil)
   205  }