-
Notifications
You must be signed in to change notification settings - Fork 29
Expand file tree
/
Copy pathmain.go
More file actions
123 lines (105 loc) · 2.64 KB
/
main.go
File metadata and controls
123 lines (105 loc) · 2.64 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
package main
import (
"context"
"errors"
"fmt"
"log/slog"
"os"
"os/signal"
"syscall"
"github.com/jingkaihe/matchlock/internal/errx"
"github.com/jingkaihe/matchlock/pkg/sdk"
"golang.org/x/term"
)
var (
errCreateClient = errors.New("create client")
errLaunchSandbox = errors.New("launch sandbox")
errTerminalRequired = errors.New("terminal required")
errSetRawMode = errors.New("set raw mode")
errRestoreTerm = errors.New("restore terminal mode")
errExecTTY = errors.New("exec_tty")
)
func main() {
if err := run(); err != nil {
slog.Error("fatal", "error", err)
os.Exit(1)
}
}
func run() error {
cfg := sdk.DefaultConfig()
if os.Getenv("MATCHLOCK_BIN") == "" {
cfg.BinaryPath = "./bin/matchlock"
}
client, err := sdk.NewClient(cfg)
if err != nil {
return errx.Wrap(errCreateClient, err)
}
defer client.Remove()
defer client.Close(0)
sandbox := sdk.New("alpine:latest").WithWorkspace("/workspace").MountMemory("/workspace")
vmID, err := client.Launch(sandbox)
if err != nil {
return errx.Wrap(errLaunchSandbox, err)
}
slog.Info("sandbox ready", "vm", vmID)
if !term.IsTerminal(int(os.Stdin.Fd())) {
return errx.With(errTerminalRequired, ": run this example in an interactive terminal")
}
ctx := context.Background()
stdinFD := int(os.Stdin.Fd())
cols, rows, err := term.GetSize(stdinFD)
if err != nil {
rows, cols = 24, 80
}
fmt.Println("Connected to sandbox shell. Type 'exit' or press Ctrl-D to quit.")
oldState, err := term.MakeRaw(stdinFD)
if err != nil {
return errx.Wrap(errSetRawMode, err)
}
restored := false
defer func() {
if !restored {
_ = term.Restore(stdinFD, oldState)
}
}()
resizeCh := make(chan [2]uint16, 4)
winchCh := make(chan os.Signal, 1)
stopResize := make(chan struct{})
signal.Notify(winchCh, syscall.SIGWINCH)
defer signal.Stop(winchCh)
defer close(stopResize)
go func() {
for {
select {
case <-stopResize:
return
case <-winchCh:
c, r, sizeErr := term.GetSize(stdinFD)
if sizeErr != nil {
continue
}
select {
case resizeCh <- [2]uint16{uint16(r), uint16(c)}:
default:
}
}
}
}()
ttyResult, err := client.ExecInteractive(ctx, "sh", &sdk.ExecInteractiveOptions{
WorkingDir: "/workspace",
Rows: uint16(rows),
Cols: uint16(cols),
Stdin: os.Stdin,
Stdout: os.Stdout,
Resize: resizeCh,
})
if err != nil {
return errx.Wrap(errExecTTY, err)
}
if err := term.Restore(stdinFD, oldState); err != nil {
return errx.Wrap(errRestoreTerm, err)
}
restored = true
fmt.Printf("\nShell exited: code=%d duration_ms=%d\n", ttyResult.ExitCode, ttyResult.DurationMS)
return nil
}