github.com/tilt-dev/tilt@v0.36.0/internal/cli/up.go (about) 1 package cli 2 3 import ( 4 "context" 5 _ "embed" 6 "errors" 7 "fmt" 8 "log" 9 "net/url" 10 "os" 11 "strconv" 12 "time" 13 14 "github.com/mattn/go-isatty" 15 "github.com/spf13/cobra" 16 17 "github.com/tilt-dev/tilt/internal/analytics" 18 "github.com/tilt-dev/tilt/internal/controllers" 19 engineanalytics "github.com/tilt-dev/tilt/internal/engine/analytics" 20 "github.com/tilt-dev/tilt/internal/hud/prompt" 21 "github.com/tilt-dev/tilt/internal/store" 22 "github.com/tilt-dev/tilt/internal/store/liveupdates" 23 "github.com/tilt-dev/tilt/pkg/assets" 24 "github.com/tilt-dev/tilt/pkg/logger" 25 "github.com/tilt-dev/tilt/pkg/model" 26 "github.com/tilt-dev/tilt/web" 27 ) 28 29 var webModeFlag model.WebMode = model.DefaultWebMode 30 31 const DefaultWebDevPort = 46764 32 33 var ( 34 updateModeFlag string = string(liveupdates.UpdateModeAuto) 35 webDevPort = 0 36 logActionsFlag bool = false 37 logSourceFlag string = "" 38 logResourcesFlag []string = nil 39 logLevelFlag string = "" 40 ) 41 42 var userExitError = errors.New("user requested Tilt exit") 43 44 //go:embed Tiltfile.starter 45 var starterTiltfile []byte 46 47 type upCmd struct { 48 fileName string 49 outputSnapshotOnExit string 50 51 legacy bool 52 stream bool 53 } 54 55 func (c *upCmd) name() model.TiltSubcommand { return "up" } 56 57 func (c *upCmd) register() *cobra.Command { 58 cmd := &cobra.Command{ 59 Use: "up [<tilt flags>] [-- <Tiltfile args>]", 60 DisableFlagsInUseLine: true, 61 Short: "Start Tilt with the given Tiltfile args", 62 Long: ` 63 Starts Tilt and runs services defined in the Tiltfile. 64 65 There are two types of args: 66 1) Tilt flags, listed below, which are handled entirely by Tilt. 67 2) Tiltfile args, which can be anything, and are potentially accessed by config.parse in your Tiltfile. 68 69 By default: 70 1) Tiltfile args are interpreted as the list of services to start, e.g. tilt up frontend backend. 71 2) Running with no Tiltfile args starts all services defined in the Tiltfile 72 73 This default behavior does not apply if the Tiltfile uses config.parse or config.set_enabled_resources. 74 In that case, see https://docs.tilt.dev/tiltfile_config.html and/or comments in your Tiltfile 75 76 When you exit Tilt (using Ctrl+C), Kubernetes resources and Docker Compose resources continue running; 77 you can use tilt down (https://docs.tilt.dev/cli/tilt_down.html) to delete these resources. Any long-running 78 local resources--i.e. those using serve_cmd--are terminated when you exit Tilt. 79 `, 80 } 81 82 cmd.Flags().StringVar(&updateModeFlag, "update-mode", string(liveupdates.UpdateModeAuto), 83 fmt.Sprintf("Control the strategy Tilt uses for updating instances. Possible values: %v", liveupdates.AllUpdateModes)) 84 cmd.Flags().BoolVar(&c.legacy, "legacy", false, "If true, tilt will open in legacy terminal mode.") 85 cmd.Flags().BoolVar(&c.stream, "stream", false, "If true, tilt will stream logs in the terminal.") 86 cmd.Flags().BoolVar(&logActionsFlag, "logactions", false, "log all actions and state changes") 87 addStartServerFlags(cmd) 88 addDevServerFlags(cmd) 89 addTiltfileFlag(cmd, &c.fileName) 90 addKubeContextFlag(cmd) 91 addNamespaceFlag(cmd) 92 addLogFilterFlags(cmd, "log-") 93 addLogFilterResourcesFlag(cmd) 94 cmd.Flags().Lookup("logactions").Hidden = true 95 cmd.Flags().StringVar(&c.outputSnapshotOnExit, "output-snapshot-on-exit", "", "If specified, Tilt will dump a snapshot of its state to the specified path when it exits") 96 97 return cmd 98 } 99 100 func (c *upCmd) initialTermMode(isTerminal bool) store.TerminalMode { 101 if !isTerminal { 102 return store.TerminalModeStream 103 } 104 105 if c.legacy { 106 return store.TerminalModeHUD 107 } 108 109 if c.stream { 110 return store.TerminalModeStream 111 } 112 113 return store.TerminalModePrompt 114 } 115 116 func (c *upCmd) run(ctx context.Context, args []string) error { 117 ctx, cancel := context.WithCancel(ctx) 118 defer cancel() 119 120 a := analytics.Get(ctx) 121 defer a.Flush(time.Second) 122 123 log.SetFlags(log.Flags() &^ (log.Ldate | log.Ltime)) 124 isTTY := isatty.IsTerminal(os.Stdout.Fd()) 125 termMode := c.initialTermMode(isTTY) 126 127 cmdUpTags := engineanalytics.CmdTags(map[string]string{ 128 "update_mode": updateModeFlag, // before 7/8/20 this was just called "mode" 129 "term_mode": strconv.Itoa(int(termMode)), 130 }) 131 132 generateTiltfileResult, err := maybeGenerateTiltfile(c.fileName) 133 // N.B. report the command before handling the error; result enum is always valid 134 cmdUpTags["generate_tiltfile.result"] = string(generateTiltfileResult) 135 a.Incr("cmd.up", cmdUpTags.AsMap()) 136 if err == userExitError { 137 return nil 138 } else if err != nil { 139 return err 140 } 141 142 deferred := logger.NewDeferredLogger(ctx) 143 ctx = redirectLogs(ctx, deferred) 144 145 webHost := provideWebHost() 146 webURL, _ := provideWebURL(webHost, provideWebPort()) 147 startLine := prompt.StartStatusLine(webURL, webHost) 148 log.Print(startLine) 149 log.Print(buildStamp()) 150 151 if ok, reason := analytics.IsAnalyticsDisabledFromEnv(); ok { 152 log.Printf("Tilt analytics disabled: %s", reason) 153 } 154 155 cmdUpDeps, err := wireCmdUp(ctx, a, cmdUpTags, "up") 156 if err != nil { 157 deferred.SetOutput(deferred.Original()) 158 return err 159 } 160 161 upper := cmdUpDeps.Upper 162 if termMode == store.TerminalModePrompt { 163 // Any logs that showed up during initialization, make sure they're 164 // in the prompt. 165 cmdUpDeps.Prompt.SetInitOutput(deferred.CopyBuffered(logger.InfoLvl)) 166 } 167 168 l := store.NewLogActionLogger(ctx, upper.Dispatch) 169 deferred.SetOutput(l) 170 ctx = redirectLogs(ctx, l) 171 if c.outputSnapshotOnExit != "" { 172 defer cmdUpDeps.Snapshotter.WriteSnapshot(ctx, c.outputSnapshotOnExit) 173 } 174 175 err = upper.Start(ctx, args, cmdUpDeps.TiltBuild, 176 c.fileName, termMode, a.UserOpt(), cmdUpDeps.Token, string(cmdUpDeps.CloudAddress)) 177 if err != context.Canceled { 178 return err 179 } else { 180 return nil 181 } 182 } 183 184 func redirectLogs(ctx context.Context, l logger.Logger) context.Context { 185 ctx = logger.WithLogger(ctx, l) 186 log.SetOutput(l.Writer(logger.InfoLvl)) 187 controllers.MaybeSetKlogOutput(l.Writer(logger.InfoLvl)) 188 return ctx 189 } 190 191 func provideUpdateModeFlag() liveupdates.UpdateModeFlag { 192 return liveupdates.UpdateModeFlag(updateModeFlag) 193 } 194 195 func provideLogActions() store.LogActionsFlag { 196 return store.LogActionsFlag(logActionsFlag) 197 } 198 199 func provideWebMode(b model.TiltBuild) (model.WebMode, error) { 200 switch webModeFlag { 201 case model.LocalWebMode, 202 model.ProdWebMode, 203 model.EmbeddedWebMode, 204 model.PrecompiledWebMode: 205 return webModeFlag, nil 206 case model.DefaultWebMode: 207 // Set prod web mode from an environment variable. Useful for 208 // running integration tests against dev tilt. 209 webMode := os.Getenv("TILT_WEB_MODE") 210 if webMode == "prod" { 211 return model.ProdWebMode, nil 212 } 213 214 if b.Dev { 215 return model.LocalWebMode, nil 216 } else { 217 return model.ProdWebMode, nil 218 } 219 } 220 return "", model.UnrecognizedWebModeError(string(webModeFlag)) 221 } 222 223 func provideWebHost() model.WebHost { 224 return model.WebHost(webHostFlag) 225 } 226 227 func provideWebPort() model.WebPort { 228 return model.WebPort(webPortFlag) 229 } 230 231 func provideWebURL(webHost model.WebHost, webPort model.WebPort) (model.WebURL, error) { 232 if webPort == 0 { 233 return model.WebURL{}, nil 234 } 235 236 if webHost == "0.0.0.0" { 237 // 0.0.0.0 means "listen on all hosts" 238 // For UI displays, we use 127.0.0.1 (loopback) 239 webHost = "127.0.0.1" 240 } 241 242 u, err := url.Parse(fmt.Sprintf("http://%s:%d/", webHost, webPort)) 243 if err != nil { 244 return model.WebURL{}, err 245 } 246 return model.WebURL(*u), nil 247 } 248 249 func targetMode(mode model.WebMode, embeddedAvailable bool) (model.WebMode, error) { 250 if (mode == model.EmbeddedWebMode || mode == model.PrecompiledWebMode) && !embeddedAvailable { 251 return mode, fmt.Errorf( 252 ("requested %s mode, but JS/CSS files are not available.\n" + 253 "Please report this: https://github.com/tilt-dev/tilt/issues"), string(mode)) 254 } 255 if mode.IsProd() { 256 // defaults to embedded, reporting an error if embedded not available. 257 if !embeddedAvailable { 258 return mode, fmt.Errorf( 259 ("running in prod mode, but JS/CSS files are not available.\n" + 260 "Please report this: https://github.com/tilt-dev/tilt/issues")) 261 } else if mode == model.ProdWebMode { 262 mode = model.EmbeddedWebMode 263 } 264 } else { // precompiled when available and by request, otherwise local 265 if mode != model.PrecompiledWebMode { 266 mode = model.LocalWebMode 267 } 268 } 269 return mode, nil 270 } 271 272 func provideAssetServer(mode model.WebMode, version model.WebVersion) (assets.Server, error) { 273 s, ok := assets.GetEmbeddedServer() 274 m, err := targetMode(mode, ok) 275 if err != nil { 276 return nil, err 277 } 278 279 switch m { 280 case model.EmbeddedWebMode, model.PrecompiledWebMode: 281 return s, nil 282 case model.LocalWebMode: 283 path, err := web.StaticPath() 284 if err != nil { 285 return nil, err 286 } 287 pkgDir := assets.PackageDir(path) 288 return assets.NewDevServer(pkgDir, model.WebDevPort(webDevPort)) 289 } 290 return nil, model.UnrecognizedWebModeError(string(mode)) 291 }