Repository navigation
Expand file tree
/
Copy pathcommands.go
More file actions
146 lines (123 loc) · 3.41 KB
/
Copy pathcommands.go
File metadata and controls
146 lines (123 loc) · 3.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package e2b
import (
"context"
"fmt"
"strings"
"time"
"connectrpc.com/connect"
processpb "github.com/matiasinsaurralde/go-e2b/internal/gen/envd/process"
"github.com/matiasinsaurralde/go-e2b/internal/gen/envd/process/processconnect"
)
// CommandResult holds the output of a completed command.
type CommandResult struct {
// Stdout is the standard output of the command.
Stdout string
// Stderr is the standard error output of the command.
Stderr string
// ExitCode is the process exit code.
ExitCode int
}
// CommandService provides command execution within a sandbox.
type CommandService struct {
sandbox *Sandbox
}
func newCommandService(sbx *Sandbox) *CommandService {
return &CommandService{sandbox: sbx}
}
// RunOption configures a single command execution.
type RunOption func(*runConfig)
type runConfig struct {
envVars map[string]string
cwd string
user string
timeout time.Duration
}
// WithEnv sets environment variables for the command.
func WithEnv(envs map[string]string) RunOption {
return func(rc *runConfig) {
rc.envVars = envs
}
}
// WithCwd sets the working directory for the command.
func WithCwd(cwd string) RunOption {
return func(rc *runConfig) {
rc.cwd = cwd
}
}
// WithUser sets the user to run the command as.
func WithUser(user string) RunOption {
return func(rc *runConfig) {
rc.user = user
}
}
// WithTimeout sets the command timeout.
// Defaults to DefaultCommandTimeout (60s).
func WithTimeout(d time.Duration) RunOption {
return func(rc *runConfig) {
rc.timeout = d
}
}
// Run executes a command in the sandbox and returns the result.
// It blocks until the command completes.
func (c *CommandService) Run(cmd string, args []string, opts ...RunOption) (*CommandResult, error) {
return c.RunWithContext(context.Background(), cmd, args, opts...)
}
// RunWithContext executes a command in the sandbox using the provided context
// for cancellation and deadline control.
func (c *CommandService) RunWithContext(ctx context.Context, cmd string, args []string, opts ...RunOption) (*CommandResult, error) {
rc := &runConfig{timeout: DefaultCommandTimeout}
for _, opt := range opts {
opt(rc)
}
if rc.timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, rc.timeout)
defer cancel()
}
cfg := &processpb.ProcessConfig{
Cmd: cmd,
Args: args,
Envs: rc.envVars,
}
if rc.cwd != "" {
cwd := rc.cwd
cfg.Cwd = &cwd
}
req := connect.NewRequest(&processpb.StartRequest{Process: cfg})
req.Header().Set("X-Access-Token", c.sandbox.accessToken)
if rc.user != "" {
req.Header().Set("User", rc.user)
}
client := processconnect.NewProcessClient(c.sandbox.client.httpClient, c.sandbox.envdBaseURL())
stream, err := client.Start(ctx, req)
if err != nil {
return nil, fmt.Errorf("e2b: start process: %w", err)
}
var stdoutBuf, stderrBuf strings.Builder
var exitCode int
for stream.Receive() {
event := stream.Msg().GetEvent()
if event == nil {
continue
}
if data := event.GetData(); data != nil {
if b := data.GetStdout(); len(b) > 0 {
stdoutBuf.Write(b)
}
if b := data.GetStderr(); len(b) > 0 {
stderrBuf.Write(b)
}
}
if end := event.GetEnd(); end != nil {
exitCode = int(end.GetExitCode())
}
}
if err := stream.Err(); err != nil {
return nil, fmt.Errorf("e2b: process stream: %w", err)
}
return &CommandResult{
Stdout: stdoutBuf.String(),
Stderr: stderrBuf.String(),
ExitCode: exitCode,
}, nil
}