github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/cli/cli.go (about) 1 package cli 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "os/signal" 8 "syscall" 9 "time" 10 11 "github.com/go-logr/logr" 12 "github.com/spf13/cobra" 13 "go.lsp.dev/protocol" 14 "k8s.io/cli-runtime/pkg/genericclioptions" 15 ctrllog "sigs.k8s.io/controller-runtime/pkg/log" 16 17 "github.com/tilt-dev/starlark-lsp/pkg/cli" 18 tiltanalytics "github.com/tilt-dev/tilt/internal/analytics" 19 "github.com/tilt-dev/tilt/internal/controllers" 20 "github.com/tilt-dev/tilt/internal/output" 21 "github.com/tilt-dev/tilt/pkg/logger" 22 "github.com/tilt-dev/tilt/pkg/model" 23 "github.com/tilt-dev/wmclient/pkg/analytics" 24 ) 25 26 var debug bool 27 var verbose bool 28 29 func logLevel(verbose, debug bool) logger.Level { 30 if debug { 31 return logger.DebugLvl 32 } else if verbose { 33 return logger.VerboseLvl 34 } else { 35 return logger.InfoLvl 36 } 37 } 38 39 func Execute() { 40 err := readEnvDefaults() 41 if err != nil { 42 fmt.Println(err) 43 os.Exit(1) 44 } 45 46 rootCmd := &cobra.Command{ 47 Use: "tilt", 48 Short: "Multi-service development with no stress", 49 Long: ` 50 Tilt helps you develop your microservices locally. 51 Run 'tilt up' to start working on your services in a complete dev environment 52 configured for your team. 53 54 Tilt watches your files for edits, automatically builds your container images, 55 and applies any changes to bring your environment 56 up-to-date in real-time. Think 'docker build && kubectl apply' or 'docker-compose up'. 57 `, 58 } 59 streams := genericclioptions.IOStreams{Out: os.Stdout, ErrOut: os.Stderr, In: os.Stdin} 60 61 addCommand(rootCmd, &ciCmd{}) 62 addCommand(rootCmd, &upCmd{}) 63 addCommand(rootCmd, &dockerCmd{}) 64 addCommand(rootCmd, &doctorCmd{}) 65 addCommand(rootCmd, newDownCmd()) 66 addCommand(rootCmd, &versionCmd{}) 67 addCommand(rootCmd, &verifyInstallCmd{}) 68 addCommand(rootCmd, &dockerPruneCmd{}) 69 addCommand(rootCmd, newArgsCmd(streams)) 70 addCommand(rootCmd, &logsCmd{}) 71 addCommand(rootCmd, newDescribeCmd(streams)) 72 addCommand(rootCmd, newGetCmd(streams)) 73 addCommand(rootCmd, newExplainCmd(streams)) 74 addCommand(rootCmd, newEditCmd(streams)) 75 addCommand(rootCmd, newApiresourcesCmd(streams)) 76 addCommand(rootCmd, newDeleteCmd(streams)) 77 addCommand(rootCmd, newApplyCmd(streams)) 78 addCommand(rootCmd, newCreateCmd(streams)) 79 addCommand(rootCmd, newPatchCmd(streams)) 80 addCommand(rootCmd, newWaitCmd(streams)) 81 addCommand(rootCmd, &demoCmd{}) 82 addCommand(rootCmd, newEnableCmd()) 83 addCommand(rootCmd, newDisableCmd()) 84 addCommand(rootCmd, newTriggerCmd(streams)) 85 86 rootCmd.AddCommand(analytics.NewCommand()) 87 rootCmd.AddCommand(newDumpCmd(rootCmd, streams)) 88 rootCmd.AddCommand(newAlphaCmd(streams)) 89 rootCmd.AddCommand(newLspCmd()) 90 rootCmd.AddCommand(newSnapshotCmd()) 91 92 globalFlags := rootCmd.PersistentFlags() 93 globalFlags.BoolVarP(&debug, "debug", "d", false, "Enable debug logging") 94 globalFlags.BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging") 95 controllers.AddKlogFlags(globalFlags) 96 97 ctx, cleanup := createContext() 98 defer cleanup() 99 if err := rootCmd.ExecuteContext(ctx); err != nil { 100 fmt.Println(err) 101 os.Exit(1) 102 } 103 } 104 105 type tiltCmd interface { 106 name() model.TiltSubcommand 107 register() *cobra.Command 108 run(ctx context.Context, args []string) error 109 } 110 111 func createContext() (ctx context.Context, cleanup func()) { 112 l, cleanup := cli.NewLogger() 113 return protocol.WithLogger(context.Background(), l), cleanup 114 } 115 116 func preCommand(ctx context.Context, cmdName model.TiltSubcommand) context.Context { 117 118 l := logger.NewLogger(logLevel(verbose, debug), os.Stdout) 119 ctx = logger.WithLogger(ctx, l) 120 121 a, err := wireAnalytics(l, cmdName) 122 if err != nil { 123 l.Errorf("Fatal error initializing analytics: %v", err) 124 os.Exit(1) 125 } 126 127 ctx = tiltanalytics.WithAnalytics(ctx, a) 128 129 // Users don't care about controller-runtime logs. 130 ctrllog.SetLogger(logr.New(ctrllog.NullLogSink{})) 131 132 controllers.InitKlog(l.Writer(logger.InfoLvl)) 133 134 // SIGNAL TRAPPING 135 ctx, cancel := context.WithCancel(ctx) 136 sigs := make(chan os.Signal, 1) 137 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) 138 go func() { 139 <-sigs 140 141 cancel() 142 143 // If we get another signal, OR it takes too long for tilt to 144 // exit after canceling context, just exit 145 select { 146 case <-sigs: 147 l.Debugf("force quitting...") 148 os.Exit(1) 149 case <-time.After(2 * time.Second): 150 l.Debugf("Context canceled but app still running; forcibly exiting.") 151 os.Exit(1) 152 } 153 }() 154 155 return ctx 156 } 157 158 func addCommand(parent *cobra.Command, child tiltCmd) { 159 cobraChild := child.register() 160 cobraChild.Run = func(cmd *cobra.Command, args []string) { 161 ctx := preCommand(cmd.Context(), child.name()) 162 163 err := child.run(ctx, args) 164 if err != nil { 165 // TODO(maia): this shouldn't print if we've already pretty-printed it 166 _, printErr := fmt.Fprintf(output.OriginalStderr, "Error: %v\n", err) 167 if printErr != nil { 168 panic(printErr) 169 } 170 os.Exit(1) 171 } 172 } 173 174 parent.AddCommand(cobraChild) 175 }