bsdkrun 0.1.0 → 0.3.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 +70 -4
- data/lib/bsdkrun/args.rb +66 -0
- data/lib/bsdkrun/client.rb +564 -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 +5 -1
- metadata +8 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b23de4b48a88b521c5909ad2aa610d8557e9b06661e450db3d862292cc3bb296
|
|
4
|
+
data.tar.gz: be6175dd5884c7ec861a57f355c3dff05d01e759355117d8e21e27904b717063
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9b2b575fc9be711c375a053537e8ba687d162c4180c1e7075aaa5e3d27d8992655615c10c28ca5894a066213a3d8382c9f6f7c31fdca7118b8dc72707bddf86f
|
|
7
|
+
data.tar.gz: 38d9ba56709b1b870df9873d70c658e68534fb2eb90fa6c6793a2e87aa80d9fe1f076a6324579f4834a9241554960b35c6501ebc05e3896a781da3166be2c565
|
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, Linux, and unikernel** 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,71 @@ 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_solo5`/`run_osv`/
|
|
217
|
+
`run_flavor` 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. `run_solo5` boots a MirageOS unikernel under the
|
|
220
|
+
`solo5-hvt` tender rather than libkrun:
|
|
221
|
+
`run_solo5(path: "dist/hello.hvt", args: ["--ipv4=10.0.0.2/24"])`.
|
|
222
|
+
`stop`/`start`/`remove`/`update`/`commit` return a
|
|
223
|
+
`CommandResult` (`exit_code`, `stdout`, `stderr`).
|
|
224
|
+
|
|
225
|
+
For a live terminal instead of a one-shot `exec`, use `shell`:
|
|
226
|
+
|
|
227
|
+
```ruby
|
|
228
|
+
session = client.shell(id) # or shell(id, command: [...]) for a non-login command
|
|
229
|
+
session.on_output { |bytes| $stdout.write(bytes) }
|
|
230
|
+
session.on_exit { |code| puts "\nexited #{code}" }
|
|
231
|
+
session.write("ls -la\n")
|
|
232
|
+
session.resize(50, 120)
|
|
233
|
+
session.close
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`follow_logs(id) { |bytes| ... }` streams a machine's console live instead of
|
|
237
|
+
the one-shot `logs(id)`. Both `exec`/`shell` and `follow_logs` are built on
|
|
238
|
+
the same `openShell`/`shellOutput` shell-session protocol the daemon uses for
|
|
239
|
+
every interactive terminal — see [`daemon/README.md`](../../daemon/README.md#interactive-shells-over-graphql)
|
|
240
|
+
for the wire-level story.
|
|
241
|
+
|
|
242
|
+
Not every GraphQL operation has a typed method yet (flavor/network/volume
|
|
243
|
+
management, for instance) — `client.request(query, variables)` runs any raw
|
|
244
|
+
query or mutation, and `client.subscribe(query, variables, on_next: ...)` runs
|
|
245
|
+
any raw subscription, for anything not wrapped above.
|
|
246
|
+
|
|
247
|
+
Like the rest of this gem, `Client` uses **only the Ruby standard library** —
|
|
248
|
+
the HTTP transport is `Net::HTTP`, and subscriptions (used by `exec`/`shell`/
|
|
249
|
+
`follow_logs`) run over a hand-rolled `graphql-transport-ws` client on top of
|
|
250
|
+
`Socket`/`OpenSSL`, since Ruby's standard library has no WebSocket client of
|
|
251
|
+
its own.
|
|
252
|
+
|
|
253
|
+
`Client.new(url:, token:)` and `.from_env` both reject a URL configured
|
|
254
|
+
without a token rather than silently making an unauthenticated request — set
|
|
255
|
+
both `BSDKRUN_URL` and `BSDKRUN_TOKEN`, or pass both explicitly.
|
|
256
|
+
|
|
194
257
|
## Errors
|
|
195
258
|
|
|
196
259
|
All errors extend `Bsdkrun::Error`:
|
|
@@ -200,6 +263,9 @@ All errors extend `Bsdkrun::Error`:
|
|
|
200
263
|
`stdout`, `stderr`, `command`). Raised by `exec` with `throw_on_error: true`,
|
|
201
264
|
by the lifecycle/namespace helpers, and by the agent helpers.
|
|
202
265
|
- `Bsdkrun::SandboxNotFound` — `Sandbox.get` matched no machine.
|
|
266
|
+
- `Bsdkrun::GraphQLError` — a `Client` request failed (carries `code`, the
|
|
267
|
+
daemon's `extensions.code`, when there is one).
|
|
268
|
+
- `Bsdkrun::AuthError` (a `GraphQLError`) — the daemon rejected the bearer token.
|
|
203
269
|
|
|
204
270
|
## Try it interactively
|
|
205
271
|
|
data/lib/bsdkrun/args.rb
CHANGED
|
@@ -65,6 +65,10 @@ 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 "solo5" then solo5_args(opts)
|
|
70
|
+
when "nanos" then nanos_args(opts)
|
|
71
|
+
when "osv" then osv_args(opts)
|
|
68
72
|
else raise ArgumentError, "unknown os: #{opts[:os].inspect}"
|
|
69
73
|
end
|
|
70
74
|
end
|
|
@@ -121,5 +125,67 @@ module Bsdkrun
|
|
|
121
125
|
a.concat(disk_args(opts)).concat(net_args(opts[:net]))
|
|
122
126
|
.concat(name_args(opts)).concat(vm_args(opts))
|
|
123
127
|
end
|
|
128
|
+
|
|
129
|
+
# @!visibility private
|
|
130
|
+
#
|
|
131
|
+
# Nanos: no agent (like unikraft), but it has a root disk, so +persist+
|
|
132
|
+
# is honored. +:image+ is a path or a ~/.ops/images name.
|
|
133
|
+
def nanos_args(opts)
|
|
134
|
+
a = ["nanos", "-d"]
|
|
135
|
+
a.push("--kernel", opts[:kernel]) if opts[:kernel]
|
|
136
|
+
a.push("--cmdline", opts[:cmdline]) if opts[:cmdline]
|
|
137
|
+
a.push("--persist") if opts[:persist]
|
|
138
|
+
a.concat(net_args(opts[:net])).concat(name_args(opts)).concat(vm_args(opts))
|
|
139
|
+
a.push(opts.fetch(:image).to_s)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# @!visibility private
|
|
143
|
+
#
|
|
144
|
+
# OSv: like nanos, no agent (no exec/shell/snapshot), but it does have a
|
|
145
|
+
# root filesystem, so +persist+ is honored. +:image+ is a loader.img, or on
|
|
146
|
+
# x86_64 the loader ELF plus a +:disk+.
|
|
147
|
+
def osv_args(opts)
|
|
148
|
+
a = ["osv", "-d"]
|
|
149
|
+
a.push("--cmdline", opts[:cmdline]) if opts[:cmdline]
|
|
150
|
+
a.push("--disk", opts[:disk]) if opts[:disk]
|
|
151
|
+
a.push("--gic", opts[:gic].to_s) if opts[:gic]
|
|
152
|
+
a.push("--persist") if opts[:persist]
|
|
153
|
+
a.concat(net_args(opts[:net])).concat(name_args(opts)).concat(vm_args(opts))
|
|
154
|
+
a.push(opts.fetch(:image).to_s)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# @!visibility private
|
|
158
|
+
#
|
|
159
|
+
# No +disk_args+: a unikernel has no disk, so there is nothing to persist,
|
|
160
|
+
# attach or clone. +:path+ is a kraft project dir or an image; default +.+.
|
|
161
|
+
def unikraft_args(opts)
|
|
162
|
+
a = ["unikraft", "-d"]
|
|
163
|
+
a.push("--cmdline", opts[:cmdline]) if opts[:cmdline]
|
|
164
|
+
a.push("--initramfs", opts[:initramfs]) if opts[:initramfs]
|
|
165
|
+
# Volumes are the exception to "no disk options": virtio-fs shares,
|
|
166
|
+
# which need neither a disk nor an agent.
|
|
167
|
+
Array(opts[:mounts]).each { |m| a.push("--mount", m) }
|
|
168
|
+
a.concat(net_args(opts[:net])).concat(name_args(opts)).concat(vm_args(opts))
|
|
169
|
+
a.push((opts[:path] || ".").to_s)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# @!visibility private
|
|
173
|
+
#
|
|
174
|
+
# Solo5 (MirageOS): runs under the +solo5-hvt+ tender rather than libkrun.
|
|
175
|
+
# The unikernel declares its devices in its own MFT1 manifest, so only the
|
|
176
|
+
# +:block+ backing files (+NAME=FILE+) are passed. +:path+ is a +.hvt+
|
|
177
|
+
# binary or a project dir whose +dist/+ holds one; default +.+. Guest
|
|
178
|
+
# +:args+ go last, after a literal +--+ — MirageOS options look like
|
|
179
|
+
# bsdkrun's own (e.g. +--ipv4=...+), so the CLI takes them as trailing
|
|
180
|
+
# args.
|
|
181
|
+
def solo5_args(opts)
|
|
182
|
+
a = ["solo5", "-d"]
|
|
183
|
+
Array(opts[:block]).each { |b| a.push("--block", b) }
|
|
184
|
+
a.concat(net_args(opts[:net])).concat(name_args(opts)).concat(vm_args(opts))
|
|
185
|
+
a.push((opts[:path] || ".").to_s)
|
|
186
|
+
args = Array(opts[:args])
|
|
187
|
+
a.push("--", *args) unless args.empty?
|
|
188
|
+
a
|
|
189
|
+
end
|
|
124
190
|
end
|
|
125
191
|
end
|