bsdkrun 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +67 -4
- data/lib/bsdkrun/args.rb +46 -0
- data/lib/bsdkrun/client.rb +543 -0
- data/lib/bsdkrun/errors.rb +25 -0
- data/lib/bsdkrun/shell_session.rb +139 -0
- data/lib/bsdkrun/types.rb +94 -2
- data/lib/bsdkrun/version.rb +1 -1
- data/lib/bsdkrun/websocket_frame.rb +116 -0
- data/lib/bsdkrun/ws_client.rb +315 -0
- data/lib/bsdkrun.rb +4 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b4ab49439569d91cb726f6aba3a2aff97bcd5ebae0fcc08daff49386d37493a6
|
|
4
|
+
data.tar.gz: 8e5e860d49fab4b63b29254f5e886e16db71e031bd1e35b0f24602e4c619c858
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6e88557a8e9eaa412bd43bcf67d25b8ed63970fbdefb42edfe81ff07b94b4df0013870c8fa1cf98ea1797ca607eb9481839934fea65eb34d0a34be507c1ee267
|
|
7
|
+
data.tar.gz: 5980d80cdbc6d908ce8c489a5728699f7ae9fcabeeac325c7043aad8d6f11051091be4631047f1d69c4870546eeda3f902f79e7c2b8ffe1ce06805c509673c71
|
data/README.md
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
# bsdkrun (Ruby SDK)
|
|
2
2
|
|
|
3
|
-
A Ruby SDK for [**bsdkrun**](https://github.com/tsirysndr/bsdkrun) — a
|
|
4
|
-
|
|
5
|
-
Linux, built on [libkrun](https://github.com/containers/libkrun). Boot and drive
|
|
6
|
-
microVMs programmatically, inspired by the **Vercel** and **Deno** Sandbox SDKs.
|
|
3
|
+
A Ruby SDK for [**bsdkrun**](https://github.com/tsirysndr/bsdkrun) — a Firecracker-style microVM launcher for **BSD and Linux** guests on macOS and
|
|
4
|
+
Linux, built on [libkrun](https://github.com/containers/libkrun). Boot and drive microVMs programmatically, inspired by the **Vercel** and **Deno** Sandbox SDKs.
|
|
7
5
|
|
|
8
6
|
The SDK shells out to the `bsdkrun` binary, so it has **zero runtime
|
|
9
7
|
dependencies** — just the Ruby standard library (`open3`, `json`, `pathname`).
|
|
@@ -191,6 +189,68 @@ Names resolve on Linux and FreeBSD via the network's DNS; **NetBSD** resolves
|
|
|
191
189
|
via a synced `/etc/hosts` block — joins auto-sync, and `Networks.sync` refreshes
|
|
192
190
|
an existing network without restarting members.
|
|
193
191
|
|
|
192
|
+
## Connecting to a remote daemon
|
|
193
|
+
|
|
194
|
+
Everything above talks to a local `bsdkrun` binary. `Bsdkrun::Client` is the
|
|
195
|
+
network sibling: it drives the same operations against a remote
|
|
196
|
+
[`bsdkrund`](../../daemon/README.md) over its GraphQL API — no local binary
|
|
197
|
+
needed, just a URL and a bearer token.
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
require "bsdkrun"
|
|
201
|
+
|
|
202
|
+
client = Bsdkrun::Client.new(url: "http://vps.example.com:50052", token: "9f2c...")
|
|
203
|
+
# or, from BSDKRUN_URL / BSDKRUN_TOKEN:
|
|
204
|
+
client = Bsdkrun::Client.from_env
|
|
205
|
+
|
|
206
|
+
machines = client.list(all: true) # Array<SandboxInfo> — same type Sandbox.list returns
|
|
207
|
+
id = client.run_linux(image: "alpine", cpus: 2, mem: 1024, command: ["sleep", "300"])
|
|
208
|
+
|
|
209
|
+
result = client.exec(id, ["uname", "-a"])
|
|
210
|
+
puts result.output, result.exit_code
|
|
211
|
+
|
|
212
|
+
client.stop(id)
|
|
213
|
+
client.remove([id])
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
`client.run_linux`/`run_bsd`/`run_nanos`/`run_unikraft`/`run_osv`/`run_flavor`
|
|
217
|
+
each take the same options as the corresponding GraphQL mutation
|
|
218
|
+
(`daemon/src/graphql.rs`) — `run_bsd(os: "freebsd", ...)`, etc. — and return
|
|
219
|
+
the new machine's id. `stop`/`start`/`remove`/`update`/`commit` return a
|
|
220
|
+
`CommandResult` (`exit_code`, `stdout`, `stderr`).
|
|
221
|
+
|
|
222
|
+
For a live terminal instead of a one-shot `exec`, use `shell`:
|
|
223
|
+
|
|
224
|
+
```ruby
|
|
225
|
+
session = client.shell(id) # or shell(id, command: [...]) for a non-login command
|
|
226
|
+
session.on_output { |bytes| $stdout.write(bytes) }
|
|
227
|
+
session.on_exit { |code| puts "\nexited #{code}" }
|
|
228
|
+
session.write("ls -la\n")
|
|
229
|
+
session.resize(50, 120)
|
|
230
|
+
session.close
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`follow_logs(id) { |bytes| ... }` streams a machine's console live instead of
|
|
234
|
+
the one-shot `logs(id)`. Both `exec`/`shell` and `follow_logs` are built on
|
|
235
|
+
the same `openShell`/`shellOutput` shell-session protocol the daemon uses for
|
|
236
|
+
every interactive terminal — see [`daemon/README.md`](../../daemon/README.md#interactive-shells-over-graphql)
|
|
237
|
+
for the wire-level story.
|
|
238
|
+
|
|
239
|
+
Not every GraphQL operation has a typed method yet (flavor/network/volume
|
|
240
|
+
management, for instance) — `client.request(query, variables)` runs any raw
|
|
241
|
+
query or mutation, and `client.subscribe(query, variables, on_next: ...)` runs
|
|
242
|
+
any raw subscription, for anything not wrapped above.
|
|
243
|
+
|
|
244
|
+
Like the rest of this gem, `Client` uses **only the Ruby standard library** —
|
|
245
|
+
the HTTP transport is `Net::HTTP`, and subscriptions (used by `exec`/`shell`/
|
|
246
|
+
`follow_logs`) run over a hand-rolled `graphql-transport-ws` client on top of
|
|
247
|
+
`Socket`/`OpenSSL`, since Ruby's standard library has no WebSocket client of
|
|
248
|
+
its own.
|
|
249
|
+
|
|
250
|
+
`Client.new(url:, token:)` and `.from_env` both reject a URL configured
|
|
251
|
+
without a token rather than silently making an unauthenticated request — set
|
|
252
|
+
both `BSDKRUN_URL` and `BSDKRUN_TOKEN`, or pass both explicitly.
|
|
253
|
+
|
|
194
254
|
## Errors
|
|
195
255
|
|
|
196
256
|
All errors extend `Bsdkrun::Error`:
|
|
@@ -200,6 +260,9 @@ All errors extend `Bsdkrun::Error`:
|
|
|
200
260
|
`stdout`, `stderr`, `command`). Raised by `exec` with `throw_on_error: true`,
|
|
201
261
|
by the lifecycle/namespace helpers, and by the agent helpers.
|
|
202
262
|
- `Bsdkrun::SandboxNotFound` — `Sandbox.get` matched no machine.
|
|
263
|
+
- `Bsdkrun::GraphQLError` — a `Client` request failed (carries `code`, the
|
|
264
|
+
daemon's `extensions.code`, when there is one).
|
|
265
|
+
- `Bsdkrun::AuthError` (a `GraphQLError`) — the daemon rejected the bearer token.
|
|
203
266
|
|
|
204
267
|
## Try it interactively
|
|
205
268
|
|
data/lib/bsdkrun/args.rb
CHANGED
|
@@ -65,6 +65,9 @@ module Bsdkrun
|
|
|
65
65
|
when "netbsd" then netbsd_args(opts)
|
|
66
66
|
when "firmware" then firmware_args(opts)
|
|
67
67
|
when "kernel" then kernel_args(opts)
|
|
68
|
+
when "unikraft" then unikraft_args(opts)
|
|
69
|
+
when "nanos" then nanos_args(opts)
|
|
70
|
+
when "osv" then osv_args(opts)
|
|
68
71
|
else raise ArgumentError, "unknown os: #{opts[:os].inspect}"
|
|
69
72
|
end
|
|
70
73
|
end
|
|
@@ -121,5 +124,48 @@ module Bsdkrun
|
|
|
121
124
|
a.concat(disk_args(opts)).concat(net_args(opts[:net]))
|
|
122
125
|
.concat(name_args(opts)).concat(vm_args(opts))
|
|
123
126
|
end
|
|
127
|
+
|
|
128
|
+
# @!visibility private
|
|
129
|
+
#
|
|
130
|
+
# Nanos: no agent (like unikraft), but it has a root disk, so +persist+
|
|
131
|
+
# is honored. +:image+ is a path or a ~/.ops/images name.
|
|
132
|
+
def nanos_args(opts)
|
|
133
|
+
a = ["nanos", "-d"]
|
|
134
|
+
a.push("--kernel", opts[:kernel]) if opts[:kernel]
|
|
135
|
+
a.push("--cmdline", opts[:cmdline]) if opts[:cmdline]
|
|
136
|
+
a.push("--persist") if opts[:persist]
|
|
137
|
+
a.concat(net_args(opts[:net])).concat(name_args(opts)).concat(vm_args(opts))
|
|
138
|
+
a.push(opts.fetch(:image).to_s)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# @!visibility private
|
|
142
|
+
#
|
|
143
|
+
# OSv: like nanos, no agent (no exec/shell/snapshot), but it does have a
|
|
144
|
+
# root filesystem, so +persist+ is honored. +:image+ is a loader.img, or on
|
|
145
|
+
# x86_64 the loader ELF plus a +:disk+.
|
|
146
|
+
def osv_args(opts)
|
|
147
|
+
a = ["osv", "-d"]
|
|
148
|
+
a.push("--cmdline", opts[:cmdline]) if opts[:cmdline]
|
|
149
|
+
a.push("--disk", opts[:disk]) if opts[:disk]
|
|
150
|
+
a.push("--gic", opts[:gic].to_s) if opts[:gic]
|
|
151
|
+
a.push("--persist") if opts[:persist]
|
|
152
|
+
a.concat(net_args(opts[:net])).concat(name_args(opts)).concat(vm_args(opts))
|
|
153
|
+
a.push(opts.fetch(:image).to_s)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# @!visibility private
|
|
157
|
+
#
|
|
158
|
+
# No +disk_args+: a unikernel has no disk, so there is nothing to persist,
|
|
159
|
+
# attach or clone. +:path+ is a kraft project dir or an image; default +.+.
|
|
160
|
+
def unikraft_args(opts)
|
|
161
|
+
a = ["unikraft", "-d"]
|
|
162
|
+
a.push("--cmdline", opts[:cmdline]) if opts[:cmdline]
|
|
163
|
+
a.push("--initramfs", opts[:initramfs]) if opts[:initramfs]
|
|
164
|
+
# Volumes are the exception to "no disk options": virtio-fs shares,
|
|
165
|
+
# which need neither a disk nor an agent.
|
|
166
|
+
Array(opts[:mounts]).each { |m| a.push("--mount", m) }
|
|
167
|
+
a.concat(net_args(opts[:net])).concat(name_args(opts)).concat(vm_args(opts))
|
|
168
|
+
a.push((opts[:path] || ".").to_s)
|
|
169
|
+
end
|
|
124
170
|
end
|
|
125
171
|
end
|