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