github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/cli/dump.go (about) 1 package cli 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 "strings" 11 12 "github.com/pkg/errors" 13 "github.com/spf13/cobra" 14 "github.com/spf13/cobra/doc" 15 "k8s.io/cli-runtime/pkg/genericclioptions" 16 17 "github.com/tilt-dev/tilt/internal/container" 18 "github.com/tilt-dev/tilt/internal/tiltfile" 19 "github.com/tilt-dev/tilt/pkg/model" 20 ) 21 22 func newDumpCmd(rootCmd *cobra.Command, streams genericclioptions.IOStreams) *cobra.Command { 23 result := &cobra.Command{ 24 Use: "dump", 25 Short: "Dump internal Tilt state", 26 Long: `Dumps internal Tilt state to stdout. 27 28 Intended to help Tilt developers inspect Tilt when things go wrong, 29 and figure out better ways to expose this info to Tilt users. 30 31 The format of the dump state does not make any API or compatibility promises, 32 and may change frequently. 33 `, 34 } 35 36 result.AddCommand(newDumpApiDocsCmd()) 37 result.AddCommand(newDumpWebviewCmd()) 38 result.AddCommand(newDumpEngineCmd()) 39 result.AddCommand(newDumpLogStoreCmd()) 40 result.AddCommand(newDumpCliDocsCmd(rootCmd)) 41 result.AddCommand(newDumpImageDeployRefCmd()) 42 addCommand(result, newOpenapiCmd(streams)) 43 44 return result 45 } 46 47 func newDumpApiDocsCmd() *cobra.Command { 48 c := &apiDocsCmd{} 49 cmd := &cobra.Command{ 50 Use: "api-docs", 51 Short: "dump the Tiltfile api documentation stub files", 52 Long: `Dumps the api documentation stub files to the provided directory. 53 54 The api stub files define the builtin functions, modules, and types used in Tiltfiles. 55 `, 56 Run: c.run, 57 Args: cobra.NoArgs, 58 } 59 cmd.Flags().StringVar(&c.dir, "dir", ".", "The directory to dump to") 60 return cmd 61 } 62 63 func newDumpWebviewCmd() *cobra.Command { 64 cmd := &cobra.Command{ 65 Use: "webview", 66 Short: "dump the state backing the webview", 67 Long: `Dumps the state backing the webview to stdout. 68 69 The webview is the JSON used to render the React UX. 70 71 The format of the dump state does not make any API or compatibility promises, 72 and may change frequently. 73 `, 74 Run: dumpWebview, 75 Args: cobra.NoArgs, 76 } 77 addConnectServerFlags(cmd) 78 return cmd 79 } 80 81 func newDumpEngineCmd() *cobra.Command { 82 cmd := &cobra.Command{ 83 Use: "engine", 84 Short: "dump the engine state", 85 Long: `Dumps the state of the Tilt engine to stdout. 86 87 The engine state is the central store where Tilt keeps all information about 88 the build specification, build history, and deployed resources. 89 90 The format of the dump state does not make any API or compatibility promises, 91 and may change frequently. 92 93 Excludes logs. 94 `, 95 Run: dumpEngine, 96 Args: cobra.NoArgs, 97 } 98 addConnectServerFlags(cmd) 99 return cmd 100 } 101 102 func newDumpLogStoreCmd() *cobra.Command { 103 cmd := &cobra.Command{ 104 Use: "logstore", 105 Short: "dump the log store", 106 Long: `Dumps the state of the Tilt log store to stdout. 107 108 Every log of a Tilt-managed resource is aggregated into a central structured log 109 store before display. Dumps the JSON representation of this store. 110 111 The format of the dump state does not make any API or compatibility promises, 112 and may change frequently. 113 `, 114 Run: dumpLogStore, 115 Args: cobra.NoArgs, 116 } 117 addConnectServerFlags(cmd) 118 return cmd 119 } 120 121 type dumpCliDocsCmd struct { 122 rootCmd *cobra.Command 123 dir string 124 } 125 126 func newDumpCliDocsCmd(rootCmd *cobra.Command) *cobra.Command { 127 c := &dumpCliDocsCmd{rootCmd: rootCmd} 128 129 cmd := &cobra.Command{ 130 Use: "cli-docs", 131 Short: "Dumps markdown docs of the CLI", 132 Args: cobra.NoArgs, 133 Run: c.run, 134 } 135 cmd.Flags().StringVar(&c.dir, "dir", ".", "The directory to dump to") 136 return cmd 137 } 138 139 func (c *dumpCliDocsCmd) filePrepender(path string) string { 140 return `--- 141 title: Tilt CLI Reference 142 layout: docs 143 sidebar: reference 144 hideEditButton: true 145 --- 146 ` 147 } 148 149 func (c *dumpCliDocsCmd) linkHandler(link string) string { 150 if strings.HasSuffix(link, ".md") { 151 return strings.TrimSuffix(link, ".md") + ".html" 152 } 153 return link 154 } 155 156 func (c *dumpCliDocsCmd) run(cmd *cobra.Command, args []string) { 157 err := doc.GenMarkdownTreeCustom(c.rootCmd, c.dir, c.filePrepender, c.linkHandler) 158 if err != nil { 159 _, _ = fmt.Fprintf(os.Stderr, "Error generating CLI docs: %v", err) 160 os.Exit(1) 161 } 162 } 163 164 func newDumpImageDeployRefCmd() *cobra.Command { 165 return &cobra.Command{ 166 Use: "image-deploy-ref REF", 167 Short: "Determine the name and tag with which Tilt will deploy the given image", 168 Long: `Determine the name and tag with which Tilt will deploy the given image. 169 170 This command is intended to be used with custom_build scripts. 171 172 Once the custom_build script has built the image at $EXPECTED_REF, it can 173 invoke: 174 175 echo $(tilt dump image-deploy-ref $EXPECTED_REF) 176 177 to print the deploy ref of the image. Tilt will read the image contents, 178 determine its hash, and create a content-based tag. 179 180 More info on custom build scripts: https://docs.tilt.dev/custom_build.html 181 `, 182 Example: "tilt dump image-deploy-ref $EXPECTED_REF", 183 Run: dumpImageDeployRef, 184 Args: cobra.ExactArgs(1), 185 } 186 } 187 188 func dumpImageDeployRef(cmd *cobra.Command, args []string) { 189 ctx := preCommand(context.Background(), "dump") 190 deps, err := wireDumpImageDeployRefDeps(ctx) 191 if err != nil { 192 _, _ = fmt.Fprintf(os.Stderr, "Initialization error: %v\n", err) 193 os.Exit(1) 194 } 195 196 // Assume that people with complex custom_build commands are using 197 // the kubernetes orchestrator. 198 deps.DockerClient.SetOrchestrator(model.OrchestratorK8s) 199 ref, err := deps.DockerBuilder.DumpImageDeployRef(ctx, args[0]) 200 if err != nil { 201 _, _ = fmt.Fprintf(os.Stderr, "%v\n", err) 202 os.Exit(1) 203 } 204 205 fmt.Printf("%s", container.FamiliarString(ref)) 206 } 207 208 func dumpWebview(cmd *cobra.Command, args []string) { 209 body := apiGet("view") 210 211 err := dumpJSON(body) 212 if err != nil { 213 cmdFail(fmt.Errorf("dump webview: %v", err)) 214 } 215 } 216 217 func dumpEngine(cmd *cobra.Command, args []string) { 218 body := apiGet("dump/engine") 219 defer func() { 220 _ = body.Close() 221 }() 222 223 result, err := decodeJSON(body) 224 if err != nil { 225 cmdFail(fmt.Errorf("dump engine: %v", err)) 226 } 227 228 obj, ok := result.(map[string]interface{}) 229 if ok { 230 delete(obj, "LogStore") 231 } 232 233 err = encodeJSON(os.Stdout, obj) 234 if err != nil { 235 cmdFail(fmt.Errorf("dump engine: %v", err)) 236 } 237 } 238 239 func dumpLogStore(cmd *cobra.Command, args []string) { 240 body := apiGet("dump/engine") 241 defer func() { 242 _ = body.Close() 243 }() 244 245 result, err := decodeJSON(body) 246 if err != nil { 247 cmdFail(fmt.Errorf("dump LogStore: %v", err)) 248 } 249 250 var logStore interface{} 251 obj, ok := result.(map[string]interface{}) 252 if ok { 253 logStore, ok = obj["LogStore"] 254 } 255 256 if !ok { 257 cmdFail(fmt.Errorf("No LogStore in engine: %v", err)) 258 } 259 260 err = encodeJSON(os.Stdout, logStore) 261 if err != nil { 262 cmdFail(fmt.Errorf("dump LogStore: %v", err)) 263 } 264 } 265 266 type apiDocsCmd struct { 267 dir string 268 } 269 270 func (a *apiDocsCmd) run(cmd *cobra.Command, args []string) { 271 stat, err := os.Stat(a.dir) 272 if err != nil || !stat.IsDir() { 273 cmdFail(fmt.Errorf("Provided name %v doesn't exist or isn't a directory", a.dir)) 274 } 275 err = tiltfile.DumpApiStubs(a.dir, tiltInfo(), func(path string, e error) { 276 if e == nil { 277 fmt.Printf("wrote %s\n", filepath.Join(a.dir, path)) 278 } 279 }) 280 if err != nil { 281 cmdFail(fmt.Errorf("dump api-docs: %v", err)) 282 } 283 } 284 285 func dumpJSON(reader io.Reader) error { 286 result, err := decodeJSON(reader) 287 if err != nil { 288 return err 289 } 290 return encodeJSON(os.Stdout, result) 291 } 292 293 func decodeJSON(reader io.Reader) (interface{}, error) { 294 decoder := json.NewDecoder(reader) 295 296 var result interface{} 297 err := decoder.Decode(&result) 298 if err != nil { 299 return nil, errors.Wrap(err, "Could not decode") 300 } 301 return result, err 302 } 303 304 func encodeJSON(w io.Writer, result interface{}) error { 305 encoder := json.NewEncoder(w) 306 encoder.SetIndent("", " ") 307 err := encoder.Encode(result) 308 if err != nil { 309 return errors.Wrap(err, "Could not print") 310 } 311 return nil 312 }