Skip to content

Commit

Permalink
cli: add tilt snapshot view command to view a snapshot from disk (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
landism committed Mar 29, 2022
1 parent f5db405 commit 78767fd
Show file tree
Hide file tree
Showing 16 changed files with 1,419 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -32,6 +32,7 @@ require (
github.com/looplab/tarjan v0.0.0-20161115091335-9cc6d6cebfb5
github.com/mattn/go-colorable v0.1.12
github.com/mattn/go-isatty v0.0.14
github.com/mattn/go-jsonpointer v0.0.1
github.com/mattn/go-tty v0.0.3
github.com/mitchellh/go-homedir v1.1.0
github.com/moby/buildkit v0.8.3
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Expand Up @@ -1130,6 +1130,8 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-jsonpointer v0.0.1 h1:j5m5P9BdP4B/zn6J7oH3KIQSOa2OHmcNAKEozUW7wuE=
github.com/mattn/go-jsonpointer v0.0.1/go.mod h1:1s8vx7JSjlgVRF+LW16MPpWSRZAxyrc1/FYzOonxeao=
github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
Expand Down
1 change: 1 addition & 0 deletions internal/cli/cli.go
Expand Up @@ -84,6 +84,7 @@ up-to-date in real-time. Think 'docker build && kubectl apply' or 'docker-compos
rootCmd.AddCommand(newDumpCmd(rootCmd, streams))
rootCmd.AddCommand(newAlphaCmd(streams))
rootCmd.AddCommand(newLspCmd())
rootCmd.AddCommand(newSnapshotCmd())

globalFlags := rootCmd.PersistentFlags()
globalFlags.BoolVarP(&debug, "debug", "d", false, "Enable debug logging")
Expand Down
162 changes: 162 additions & 0 deletions internal/cli/snapshot.go
@@ -0,0 +1,162 @@
package cli

import (
"context"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"time"

"github.com/mattn/go-tty"
"github.com/pkg/browser"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"

"github.com/tilt-dev/tilt/internal/analytics"
engineanalytics "github.com/tilt-dev/tilt/internal/engine/analytics"
"github.com/tilt-dev/tilt/internal/snapshots"
)

func newSnapshotCmd() *cobra.Command {
result := &cobra.Command{
Use: "snapshot",
}

result.AddCommand(newViewCommand())

return result
}

type serveCmd struct {
noOpen bool
}

func newViewCommand() *cobra.Command {
c := &serveCmd{}
result := &cobra.Command{
Use: "view <path/to/snapshot.json>",
Short: "Serves the specified snapshot file and optionally opens it in the browser",
Long: "Serves the specified snapshot file and optionally opens it in the browser",
Example: `
# Run tilt ci and save a snapshot
tilt ci --output-snapshot-on-exit=snapshot.json
# View that snapshot
tilt snapshot view snapshot.json
# Or pipe the snapshot to stdin and specify the snapshot as '-'
curl http://myci.com/path/to/snapshot | tilt snapshot view -
`,
Args: cobra.ExactArgs(1),
Run: c.run,
}

result.Flags().BoolVar(&c.noOpen, "no-open", false, "Do not automatically open the snapshot in the browser")

addStartServerFlags(result)

return result
}

// blocks until any key is pressed or ctx is canceled
func waitForKey(ctx context.Context) error {
t, err := tty.Open()
if err != nil {
return err
}
defer func() { _ = t.Close() }()

done := make(chan struct{})
errCh := make(chan error)

go func() {
_, err = t.ReadRune()
if err != nil {
errCh <- err
return
}
close(done)
}()

select {
case <-ctx.Done():
return nil
case <-done:
return nil
case err := <-errCh:
return err
}
}

func (c *serveCmd) run(_ *cobra.Command, args []string) {
err := c.serveSnapshot(args[0])
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v", err)
os.Exit(1)
}
}

func readSnapshot(snapshotArg string) ([]byte, error) {
var r io.Reader
if snapshotArg == "-" {
r = os.Stdin
} else {
f, err := os.Open(snapshotArg)
if err != nil {
return nil, err
}
r = f
defer func() { _ = f.Close() }()
}

return ioutil.ReadAll(r)
}

func (c *serveCmd) serveSnapshot(snapshotPath string) error {
ctx := preCommand(context.Background(), "snapshot view")
a := analytics.Get(ctx)
cmdTags := engineanalytics.CmdTags(map[string]string{})
a.Incr("cmd.snapshot.view", cmdTags.AsMap())
defer a.Flush(time.Second)

url := fmt.Sprintf("http://localhost:%d/snapshot/local", webPortFlag)

fmt.Printf("Serving snapshot at %s\n", url)

wg, ctx := errgroup.WithContext(ctx)
wg.Go(func() error {
snapshot, err := readSnapshot(snapshotPath)
if err != nil {
return err
}
return snapshots.Serve(ctx, snapshot, webPortFlag)
})

// give the server a little bit of time to spin up
time.Sleep(200 * time.Millisecond)

if !c.noOpen {
err := browser.OpenURL(url)
if err != nil {
return err
}
}

keyPressed := errors.New("pressed key to exit")
wg.Go(func() error {
fmt.Println("Press any key to exit")
err := waitForKey(ctx)
if err != nil {
return err
}
return keyPressed
})

err := wg.Wait()
if err != nil && err != keyPressed {
return err
}

return nil
}
82 changes: 82 additions & 0 deletions internal/snapshots/server.go
@@ -0,0 +1,82 @@
package snapshots

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"

"github.com/tilt-dev/tilt/pkg/assets"
"github.com/tilt-dev/tilt/pkg/model"
pkgsnapshot "github.com/tilt-dev/tilt/pkg/snapshot"
)

func Serve(ctx context.Context, rawSnapshot []byte, port int) error {
buf := bytes.NewReader(rawSnapshot)
var snapshot map[string]interface{}

err := json.NewDecoder(buf).Decode(&snapshot)
if err != nil {
return err
}

version, err := pkgsnapshot.GetVersionFromSnapshot(snapshot)
if err != nil {
return err
}

ss, err := newSnapshotServer(rawSnapshot, version)
if err != nil {
return err
}

go func() {
<-ctx.Done()
_ = ss.server.Shutdown(context.Background())
}()

err = ss.serve(port)
if err != nil && err != http.ErrServerClosed {
return err
}
return nil
}

type snapshotServer struct {
assetServer assets.Server
snapshot []byte
server http.Server
}

func newSnapshotServer(snapshot []byte, version string) (*snapshotServer, error) {
result := &snapshotServer{}

s, err := assets.NewProdServer(assets.ProdAssetBucket, model.WebVersion(version))
if err != nil {
return result, err
}
result.assetServer = s
result.snapshot = snapshot

return result, nil
}

func (ss *snapshotServer) serve(port int) error {
m := http.NewServeMux()

m.HandleFunc("/api/snapshot/local", ss.snapshotJSONHandler(ss.snapshot))
m.HandleFunc("/", ss.assetServer.ServeHTTP)

ss.server = http.Server{Addr: fmt.Sprintf("0:%d", port), Handler: m}
return ss.server.ListenAndServe()
}

func (ss *snapshotServer) snapshotJSONHandler(snapshot []byte) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
_, err := w.Write(snapshot)
if err != nil {
http.Error(w, fmt.Sprintf("error writing snapshot to http response: %v", err.Error()), http.StatusInternalServerError)
}
}
}
1 change: 1 addition & 0 deletions pkg/snapshot/testdata/snapshot.json

Large diffs are not rendered by default.

511 changes: 511 additions & 0 deletions pkg/snapshot/testdata/snapshot_new.json

Large diffs are not rendered by default.

0 comments on commit 78767fd

Please sign in to comment.