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 }