1
0
forked from wrenn/wrenn

fix exec cwd and env propagation

This commit is contained in:
Tasnim Kabir Sadik
2026-05-15 15:06:32 +06:00
parent c08884fa2c
commit 239a642497
8 changed files with 186 additions and 118 deletions

View File

@ -142,6 +142,8 @@ func (h *execHandler) Exec(w http.ResponseWriter, r *http.Request) {
Cmd: req.Cmd,
Args: req.Args,
TimeoutSec: req.TimeoutSec,
Envs: req.Envs,
Cwd: req.Cwd,
}))
if err != nil {
status, code, msg := agentErrToHTTP(err)

View File

@ -35,9 +35,11 @@ var upgrader = websocket.Upgrader{
// wsStartMsg is the first message the client sends to start a process.
type wsStartMsg struct {
Type string `json:"type"` // "start"
Cmd string `json:"cmd"`
Args []string `json:"args"`
Type string `json:"type"` // "start"
Cmd string `json:"cmd"`
Args []string `json:"args"`
Envs map[string]string `json:"envs"`
Cwd string `json:"cwd"`
}
// wsOutMsg is sent by the server for process events.
@ -133,6 +135,8 @@ func (h *execStreamHandler) runExecStream(ctx context.Context, conn *websocket.C
SandboxId: sandboxIDStr,
Cmd: startMsg.Cmd,
Args: startMsg.Args,
Envs: startMsg.Envs,
Cwd: startMsg.Cwd,
}))
if err != nil {
sendWSError(conn, "failed to start exec stream: "+err.Error())

View File

@ -80,14 +80,20 @@ type ExecResult struct {
// Exec runs a command inside the sandbox and collects all stdout/stderr output.
// It blocks until the command completes.
func (c *Client) Exec(ctx context.Context, cmd string, args ...string) (*ExecResult, error) {
func (c *Client) Exec(ctx context.Context, cmd string, args []string, envs map[string]string, cwd string) (*ExecResult, error) {
stdin := false
cfg := &envdpb.ProcessConfig{
Cmd: cmd,
Args: args,
Envs: envs,
}
if cwd != "" {
cfg.Cwd = &cwd
}
req := connect.NewRequest(&envdpb.StartRequest{
Process: &envdpb.ProcessConfig{
Cmd: cmd,
Args: args,
},
Stdin: &stdin,
Process: cfg,
Stdin: &stdin,
})
stream, err := c.process.Start(ctx, req)
@ -150,14 +156,20 @@ type ExecStreamEvent struct {
// ExecStream runs a command inside the sandbox and returns a channel of output events.
// The channel is closed when the process ends or the context is cancelled.
func (c *Client) ExecStream(ctx context.Context, cmd string, args ...string) (<-chan ExecStreamEvent, error) {
func (c *Client) ExecStream(ctx context.Context, cmd string, args []string, envs map[string]string, cwd string) (<-chan ExecStreamEvent, error) {
stdin := false
cfg := &envdpb.ProcessConfig{
Cmd: cmd,
Args: args,
Envs: envs,
}
if cwd != "" {
cfg.Cwd = &cwd
}
req := connect.NewRequest(&envdpb.StartRequest{
Process: &envdpb.ProcessConfig{
Cmd: cmd,
Args: args,
},
Stdin: &stdin,
Process: cfg,
Stdin: &stdin,
})
stream, err := c.process.Start(ctx, req)

View File

@ -215,7 +215,7 @@ func (s *Server) Exec(
execCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
result, err := s.mgr.Exec(execCtx, msg.SandboxId, msg.Cmd, msg.Args...)
result, err := s.mgr.Exec(execCtx, msg.SandboxId, msg.Cmd, msg.Args, msg.Envs, msg.Cwd)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("exec: %w", err))
}
@ -342,7 +342,7 @@ func (s *Server) ExecStream(
defer cancel()
}
events, err := s.mgr.ExecStream(execCtx, msg.SandboxId, msg.Cmd, msg.Args...)
events, err := s.mgr.ExecStream(execCtx, msg.SandboxId, msg.Cmd, msg.Args, msg.Envs, msg.Cwd)
if err != nil {
return connect.NewError(connect.CodeInternal, fmt.Errorf("exec stream: %w", err))
}

View File

@ -1050,7 +1050,7 @@ func (m *Manager) FlattenRootfs(ctx context.Context, sandboxID string, teamID, t
func() {
syncCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
if _, err := sb.client.Exec(syncCtx, "/bin/sync"); err != nil {
if _, err := sb.client.Exec(syncCtx, "/bin/sync", nil, nil, ""); err != nil {
slog.Warn("flatten: guest sync failed (non-fatal)", "id", sb.ID, "error", err)
}
}()
@ -1352,7 +1352,7 @@ func (m *Manager) createFromSnapshot(ctx context.Context, sandboxID string, team
}
// Exec runs a command inside a sandbox.
func (m *Manager) Exec(ctx context.Context, sandboxID string, cmd string, args ...string) (*envdclient.ExecResult, error) {
func (m *Manager) Exec(ctx context.Context, sandboxID string, cmd string, args []string, envs map[string]string, cwd string) (*envdclient.ExecResult, error) {
sb, err := m.get(sandboxID)
if err != nil {
return nil, err
@ -1366,11 +1366,11 @@ func (m *Manager) Exec(ctx context.Context, sandboxID string, cmd string, args .
sb.LastActiveAt = time.Now()
m.mu.Unlock()
return sb.client.Exec(ctx, cmd, args...)
return sb.client.Exec(ctx, cmd, args, envs, cwd)
}
// ExecStream runs a command inside a sandbox and returns a channel of streaming events.
func (m *Manager) ExecStream(ctx context.Context, sandboxID string, cmd string, args ...string) (<-chan envdclient.ExecStreamEvent, error) {
func (m *Manager) ExecStream(ctx context.Context, sandboxID string, cmd string, args []string, envs map[string]string, cwd string) (<-chan envdclient.ExecStreamEvent, error) {
sb, err := m.get(sandboxID)
if err != nil {
return nil, err
@ -1384,7 +1384,7 @@ func (m *Manager) ExecStream(ctx context.Context, sandboxID string, cmd string,
sb.LastActiveAt = time.Now()
m.mu.Unlock()
return sb.client.ExecStream(ctx, cmd, args...)
return sb.client.ExecStream(ctx, cmd, args, envs, cwd)
}
// List returns all sandboxes.

View File

@ -143,8 +143,8 @@ func (c *fcClient) setMMDS(ctx context.Context, sandboxID, templateID string) er
// Must be called before startVM.
func (c *fcClient) setBalloon(ctx context.Context, amountMiB int, deflateOnOom bool, statsIntervalS int) error {
return c.do(ctx, http.MethodPut, "/balloon", map[string]any{
"amount_mib": amountMiB,
"deflate_on_oom": deflateOnOom,
"amount_mib": amountMiB,
"deflate_on_oom": deflateOnOom,
"stats_polling_interval_s": statsIntervalS,
})
}