Skip to content

Commit

Permalink
Expose app root path to front-end (#882)
Browse files Browse the repository at this point in the history
So that we can open the files in the users editor
  • Loading branch information
simon-johansson authored Sep 14, 2023
1 parent 9166645 commit 99069d9
Show file tree
Hide file tree
Showing 23 changed files with 1,514 additions and 100 deletions.
6 changes: 4 additions & 2 deletions cli/cmd/encore/cmdutil/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

"encr.dev/cli/internal/xos"
"encr.dev/internal/version"
"encr.dev/pkg/xos"
daemonpb "encr.dev/proto/encore/daemon"
)

Expand Down Expand Up @@ -55,7 +55,7 @@ func ConnectDaemon(ctx context.Context) daemonpb.DaemonClient {
}
}
// Remove the socket file which triggers the daemon to exit.
os.Remove(socketPath)
_ = os.Remove(socketPath)
}

// Start the daemon.
Expand Down Expand Up @@ -95,13 +95,15 @@ func StartDaemonInBackground(ctx context.Context) error {
return err
}

// nosemgrep
exe, err := os.Executable()
if err != nil {
exe, err = exec.LookPath("encore")
}
if err != nil {
return fmt.Errorf("could not determine location of encore executable: %v", err)
}
// nosemgrep
cmd := exec.Command(exe, "daemon", "-f")
cmd.SysProcAttr = xos.CreateNewProcessGroup()
if err := cmd.Start(); err != nil {
Expand Down
4 changes: 2 additions & 2 deletions cli/cmd/encore/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ import (
"encr.dev/cli/daemon/sqldb"
"encr.dev/cli/daemon/sqldb/docker"
"encr.dev/cli/daemon/sqldb/external"
"encr.dev/cli/internal/xos"
"encr.dev/internal/conf"
"encr.dev/pkg/eerror"
"encr.dev/pkg/watcher"
"encr.dev/pkg/xos"
daemonpb "encr.dev/proto/encore/daemon"
)

Expand Down Expand Up @@ -239,7 +239,7 @@ func (d *Daemon) serveDBProxy() {

func (d *Daemon) serveDash() {
log.Info().Stringer("addr", d.Dash.Addr()).Msg("serving dash")
srv := dash.NewServer(d.RunMgr, d.Trace, d.Dash.Port())
srv := dash.NewServer(d.Apps, d.RunMgr, d.Trace, d.Dash.Port())
d.exit <- http.Serve(d.Dash, srv)
}

Expand Down
29 changes: 0 additions & 29 deletions cli/daemon/dash/context.go

This file was deleted.

36 changes: 0 additions & 36 deletions cli/daemon/dash/context_test.go

This file was deleted.

138 changes: 109 additions & 29 deletions cli/daemon/dash/dash.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,36 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"

"github.com/golang/protobuf/jsonpb"
"github.com/rs/zerolog/log"
"github.com/tailscale/hujson"

"encr.dev/cli/daemon/apps"
"encr.dev/cli/daemon/engine/trace2"
"encr.dev/cli/daemon/run"
"encr.dev/cli/internal/browser"
"encr.dev/cli/internal/jsonrpc2"
"encr.dev/internal/version"
"encr.dev/parser/encoding"
"encr.dev/pkg/editors"
"encr.dev/pkg/errlist"
"encr.dev/pkg/fns"
tracepb2 "encr.dev/proto/encore/engine/trace2"
v1 "encr.dev/proto/encore/parser/meta/v1"
)

type handler struct {
rpc jsonrpc2.Conn
run *run.Manager
tr trace2.Store
rpc jsonrpc2.Conn
apps *apps.Manager
run *run.Manager
tr trace2.Store
}

func (h *handler) Handle(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error {
Expand All @@ -45,10 +48,24 @@ func (h *handler) Handle(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2
}

switch r.Method() {
case "version":
type versionResp struct {
Version string `json:"version"`
Channel string `json:"channel"`
}

rtn := versionResp{
Version: version.Version,
Channel: string(version.Channel),
}

return reply(ctx, rtn, nil)

case "list-apps":
type app struct {
ID string `json:"id"`
Name string `json:"name"`
ID string `json:"id"`
Name string `json:"name"`
AppRoot string `json:"app_root"`
}
runs := h.run.ListRuns()
apps := []app{} // prevent marshalling as null
Expand All @@ -61,7 +78,7 @@ func (h *handler) Handle(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2
}
if !seen[id] {
seen[id] = true
apps = append(apps, app{ID: id, Name: name})
apps = append(apps, app{ID: id, Name: name, AppRoot: r.App.Root()})
}
}
return reply(ctx, apps, nil)
Expand Down Expand Up @@ -119,13 +136,35 @@ func (h *handler) Handle(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2
return reply(ctx, nil, err)
}

run := h.run.FindRunByAppID(params.AppID)
if run == nil {
return reply(ctx, map[string]interface{}{"running": false}, nil)
// Find the latest app by platform ID or local ID.
app, err := h.apps.FindLatestByPlatformOrLocalID(params.AppID)
if err != nil {
if errors.Is(err, apps.ErrNotFound) {
return reply(ctx, map[string]interface{}{"running": false}, nil)
} else {
return reply(ctx, nil, err)
}
}
proc := run.ProcGroup()
if proc == nil {
return reply(ctx, map[string]interface{}{"running": false}, nil)

type statusResponse struct {
Running bool `json:"running"`
AppID string `json:"appID"`
PID string `json:"pid,omitempty"`
Meta json.RawMessage `json:"meta,omitempty"`
Addr string `json:"addr,omitempty"`
APIEncoding *encoding.APIEncoding `json:"apiEncoding,omitempty"`
AppRoot string `json:"appRoot"`
}

// Now find the running instance(s)
runInstance := h.run.FindRunByAppID(params.AppID)
proc := runInstance.ProcGroup()
if runInstance == nil || proc == nil {
return reply(ctx, statusResponse{
Running: false,
AppID: app.PlatformOrLocalID(),
AppRoot: app.Root(),
}, nil)
}

m := &jsonpb.Marshaler{OrigName: true, EmitDefaults: true}
Expand All @@ -136,13 +175,14 @@ func (h *handler) Handle(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2
}

apiEnc := encoding.DescribeAPI(proc.Meta)
return reply(ctx, map[string]interface{}{
"running": true,
"appID": run.App.PlatformOrLocalID(),
"pid": run.ID,
"meta": json.RawMessage(str),
"addr": run.ListenAddr,
"apiEncoding": apiEnc,
return reply(ctx, statusResponse{
Running: true,
AppID: app.PlatformOrLocalID(),
PID: runInstance.ID,
Meta: json.RawMessage(str),
Addr: runInstance.ListenAddr,
APIEncoding: apiEnc,
AppRoot: app.Root(),
}, nil)

case "api-call":
Expand All @@ -152,25 +192,65 @@ func (h *handler) Handle(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2
}
return h.apiCall(ctx, reply, &params)

case "source-context":
case "editors/list":
var resp struct {
Editors []string `json:"editors"`
}

found, err := editors.Resolve(ctx)
if err != nil {
log.Err(err).Msg("dash: could not list editors")
return reply(ctx, nil, err)
}

for _, e := range found {
resp.Editors = append(resp.Editors, string(e.Editor))
}
return reply(ctx, resp, nil)

case "editors/open":
var params struct {
AppID string
File string
Line int
AppID string `json:"app_id"`
Editor editors.EditorName `json:"editor"`
File string `json:"file"`
StartLine int `json:"start_line,omitempty"`
StartCol int `json:"start_col,omitempty"`
EndLine int `json:"end_line,omitempty"`
EndCol int `json:"end_col,omitempty"`
}
if err := unmarshal(&params); err != nil {
log.Warn().Err(err).Msg("dash: could not parse open command")
return reply(ctx, nil, err)
}
f, err := os.Open(params.File)

editor, err := editors.Find(ctx, params.Editor)
if err != nil {
log.Err(err).Str("editor", string(params.Editor)).Msg("dash: could not find editor")
return reply(ctx, nil, err)
}
defer fns.CloseIgnore(f)
lines, start, err := sourceContext(f, params.Line, 5)

app, err := h.apps.FindLatestByPlatformOrLocalID(params.AppID)
if err != nil {
if errors.Is(err, apps.ErrNotFound) {
return reply(ctx, nil, fmt.Errorf("app not found, try running encore run"))
}
log.Err(err).Str("app_id", params.AppID).Msg("dash: could not find app")
return reply(ctx, nil, err)
}
return reply(ctx, sourceContextResponse{Lines: lines, Start: start}, nil)

if !filepath.IsLocal(params.File) {
log.Warn().Str("file", params.File).Msg("dash: file was not local to the repo")
return reply(ctx, nil, errors.New("file path must be local"))
}
params.File = filepath.Join(app.Root(), params.File)

if err := editors.LaunchExternalEditor(params.File, params.StartLine, params.StartCol, editor); err != nil {
log.Err(err).Str("editor", string(params.Editor)).Msg("dash: could not open file")
return reply(ctx, nil, err)
}

type openResp struct{}
return reply(ctx, openResp{}, nil)
}

return jsonrpc2.MethodNotFound(ctx, reply, r)
Expand Down
7 changes: 5 additions & 2 deletions cli/daemon/dash/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/gorilla/websocket"
"github.com/rs/zerolog/log"

"encr.dev/cli/daemon/apps"
"encr.dev/cli/daemon/dash/dashproxy"
"encr.dev/cli/daemon/engine/trace2"
"encr.dev/cli/daemon/run"
Expand All @@ -25,14 +26,15 @@ var upgrader = websocket.Upgrader{
}

// NewServer starts a new server and returns it.
func NewServer(runMgr *run.Manager, tr trace2.Store, dashPort int) *Server {
func NewServer(appsMgr *apps.Manager, runMgr *run.Manager, tr trace2.Store, dashPort int) *Server {
proxy, err := dashproxy.New(conf.DevDashURL)
if err != nil {
log.Fatal().Err(err).Msg("could not create dash proxy")
}

s := &Server{
proxy: proxy,
apps: appsMgr,
run: runMgr,
tr: tr,
dashPort: dashPort,
Expand All @@ -49,6 +51,7 @@ func NewServer(runMgr *run.Manager, tr trace2.Store, dashPort int) *Server {
// Server is the http.Handler for serving the developer dashboard.
type Server struct {
proxy *httputil.ReverseProxy
apps *apps.Manager
run *run.Manager
tr trace2.Store
dashPort int
Expand Down Expand Up @@ -79,7 +82,7 @@ func (s *Server) WebSocket(w http.ResponseWriter, req *http.Request) {

stream := &wsStream{c: c}
conn := jsonrpc2.NewConn(stream)
handler := &handler{rpc: conn, run: s.run, tr: s.tr}
handler := &handler{rpc: conn, apps: s.apps, run: s.run, tr: s.tr}
conn.Go(req.Context(), handler.Handle)

ch := make(chan *notification, 20)
Expand Down
6 changes: 6 additions & 0 deletions cli/daemon/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,13 @@ type RunLogger interface {
// ProcGroup returns the current running process.
// It may have already exited.
// If the proc has not yet started it may return nil.
//
// If run is nil then nil will be returned
func (r *Run) ProcGroup() *ProcGroup {
if r == nil {
return nil
}

p, _ := r.proc.Load().(*ProcGroup)
return p
}
Expand Down
Loading

0 comments on commit 99069d9

Please sign in to comment.