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  }