microsandbox-rb 0.5.8 → 0.5.9

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.
@@ -0,0 +1,247 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Microsandbox
4
+ # The result of an {SshClient#exec} call. Like {ExecOutput}, `stdout`/`stderr`
5
+ # are the captured bytes decoded as UTF-8 (lenient); use `stdout_bytes`/
6
+ # `stderr_bytes` for the raw ASCII-8BIT bytes.
7
+ class SshOutput
8
+ # @return [Integer] the remote command's exit status
9
+ attr_reader :status
10
+ # @return [String] raw stdout bytes (ASCII-8BIT)
11
+ attr_reader :stdout_bytes
12
+ # @return [String] raw stderr bytes (ASCII-8BIT)
13
+ attr_reader :stderr_bytes
14
+
15
+ def initialize(data)
16
+ @status = data["status"]
17
+ @success = data["success"]
18
+ @stdout_bytes = data["stdout"]
19
+ @stderr_bytes = data["stderr"]
20
+ end
21
+
22
+ # @return [Boolean] whether the command exited with status 0
23
+ def success? = @success
24
+
25
+ # @return [Boolean] whether the command exited non-zero
26
+ def failure? = !@success
27
+
28
+ # @return [String] stdout decoded as UTF-8
29
+ def stdout
30
+ @stdout ||= @stdout_bytes.dup.force_encoding(Encoding::UTF_8)
31
+ end
32
+
33
+ # @return [String] stderr decoded as UTF-8
34
+ def stderr
35
+ @stderr ||= @stderr_bytes.dup.force_encoding(Encoding::UTF_8)
36
+ end
37
+
38
+ def to_s = stdout
39
+
40
+ def inspect
41
+ "#<Microsandbox::SshOutput status=#{@status} success=#{@success} " \
42
+ "stdout=#{stdout.bytesize}B stderr=#{stderr.bytesize}B>"
43
+ end
44
+ end
45
+
46
+ # A high-level SFTP session over an {SshClient}, from {SshClient#sftp}. All
47
+ # paths are guest paths. Mirrors the `SftpClient` of the official SDKs.
48
+ class SftpClient
49
+ def initialize(native)
50
+ @native = native
51
+ end
52
+
53
+ # Read a file's full contents.
54
+ # @return [String] raw bytes (ASCII-8BIT)
55
+ def read(path)
56
+ @native.read(path.to_s)
57
+ end
58
+
59
+ # Read a file and decode it as UTF-8 (lenient).
60
+ # @return [String]
61
+ def read_text(path)
62
+ read(path).force_encoding(Encoding::UTF_8)
63
+ end
64
+
65
+ # Write a file, creating or truncating it.
66
+ # @return [nil]
67
+ def write(path, data)
68
+ @native.write(path.to_s, data.to_s)
69
+ nil
70
+ end
71
+
72
+ # Create a directory.
73
+ # @return [nil]
74
+ def mkdir(path)
75
+ @native.mkdir(path.to_s)
76
+ nil
77
+ end
78
+
79
+ # Remove a file.
80
+ # @return [nil]
81
+ def remove_file(path)
82
+ @native.remove_file(path.to_s)
83
+ nil
84
+ end
85
+
86
+ # Remove an empty directory.
87
+ # @return [nil]
88
+ def remove_dir(path)
89
+ @native.remove_dir(path.to_s)
90
+ nil
91
+ end
92
+
93
+ # Rename (move) a file or directory.
94
+ # @return [nil]
95
+ def rename(old_path, new_path)
96
+ @native.rename(old_path.to_s, new_path.to_s)
97
+ nil
98
+ end
99
+
100
+ # Create a symlink at +link_path+ pointing to +target+.
101
+ # @return [nil]
102
+ def symlink(target, link_path)
103
+ @native.symlink(target.to_s, link_path.to_s)
104
+ nil
105
+ end
106
+
107
+ # Resolve a path to its canonical absolute form.
108
+ # @return [String]
109
+ def real_path(path)
110
+ @native.real_path(path.to_s)
111
+ end
112
+
113
+ # Read a symlink's target.
114
+ # @return [String]
115
+ def read_link(path)
116
+ @native.read_link(path.to_s)
117
+ end
118
+
119
+ # Close the SFTP session. Idempotent.
120
+ # @return [nil]
121
+ def close
122
+ @native.close
123
+ nil
124
+ end
125
+ end
126
+
127
+ # A native, in-process SSH client session to a sandbox, from
128
+ # {SshOps#open_client}. Mirrors the `SshClient` of the official SDKs.
129
+ #
130
+ # @example
131
+ # sb.ssh.open_client do |client|
132
+ # out = client.exec("uname -a")
133
+ # puts out.stdout
134
+ # end
135
+ class SshClient
136
+ def initialize(native)
137
+ @native = native
138
+ end
139
+
140
+ # Run a command over SSH and collect its output.
141
+ # @param command [String] the command line (interpreted by the remote shell)
142
+ # @param tty [Boolean] allocate a pseudo-terminal
143
+ # @return [SshOutput]
144
+ def exec(command, tty: false)
145
+ SshOutput.new(@native.exec(command.to_s, tty ? true : false))
146
+ end
147
+
148
+ # Attach the local terminal to an interactive SSH shell. Host-TTY coupled
149
+ # (puts the terminal in raw mode and forwards SIGWINCH); blocks until the
150
+ # remote shell exits or the detach sequence is typed.
151
+ # @param term [String, nil] TERM value to request (defaults to $TERM)
152
+ # @param detach_keys [String, nil] detach key sequence (e.g. "ctrl-p,ctrl-q")
153
+ # @return [Integer] the remote shell's exit status
154
+ def attach(term: nil, detach_keys: nil)
155
+ @native.attach(term&.to_s, detach_keys&.to_s)
156
+ end
157
+
158
+ # Open an SFTP session over this connection. With a block, the session is
159
+ # yielded and closed when the block returns.
160
+ # @yieldparam sftp [SftpClient]
161
+ # @return [SftpClient, Object]
162
+ def sftp
163
+ session = SftpClient.new(@native.sftp)
164
+ return session unless block_given?
165
+
166
+ begin
167
+ yield session
168
+ ensure
169
+ session.close
170
+ end
171
+ end
172
+
173
+ # Close the SSH client session. Idempotent.
174
+ # @return [nil]
175
+ def close
176
+ @native.close
177
+ nil
178
+ end
179
+ end
180
+
181
+ # A reusable SSH server endpoint for a sandbox, from {SshOps#prepare_server}.
182
+ # Each {#serve_connection} serves a single SSH transport over this process's
183
+ # stdin/stdout — typically wired up by a parent SSH daemon via `ForceCommand`
184
+ # or an inetd-style spawn. Mirrors the `SshServer` of the official SDKs.
185
+ class SshServer
186
+ def initialize(native)
187
+ @native = native
188
+ end
189
+
190
+ # Serve one SSH connection over this process's stdin/stdout. Blocks until
191
+ # the session ends.
192
+ # @return [nil]
193
+ def serve_connection
194
+ @native.serve_connection
195
+ nil
196
+ end
197
+
198
+ # Release the prepared server endpoint. Idempotent.
199
+ # @return [nil]
200
+ def close
201
+ @native.close
202
+ nil
203
+ end
204
+ end
205
+
206
+ # The SSH namespace for a sandbox, returned by {Sandbox#ssh}. Use it to open a
207
+ # native in-process SSH client or prepare a reusable server endpoint.
208
+ class SshOps
209
+ def initialize(native)
210
+ @native = native
211
+ end
212
+
213
+ # Open a native in-process SSH client to the sandbox. With a block, the
214
+ # client is yielded and closed when the block returns.
215
+ # @param user [String] guest user to authenticate as (default "root")
216
+ # @param term [String, nil] TERM value for the session
217
+ # @param sftp [Boolean] enable the SFTP subsystem (default true)
218
+ # @yieldparam client [SshClient]
219
+ # @return [SshClient, Object]
220
+ def open_client(user: "root", term: nil, sftp: true)
221
+ opts = { "user" => user.to_s, "sftp" => sftp ? true : false }
222
+ opts["term"] = term.to_s if term
223
+ client = SshClient.new(@native.ssh_open_client(opts))
224
+ return client unless block_given?
225
+
226
+ begin
227
+ yield client
228
+ ensure
229
+ client.close
230
+ end
231
+ end
232
+
233
+ # Prepare a reusable SSH server endpoint for the sandbox.
234
+ # @param host_key_path [String, nil] PEM host key path (generated if omitted)
235
+ # @param authorized_keys_path [String, nil] authorized_keys file path
236
+ # @param user [String, nil] guest user connections run as
237
+ # @param sftp [Boolean] enable the SFTP subsystem (default true)
238
+ # @return [SshServer]
239
+ def prepare_server(host_key_path: nil, authorized_keys_path: nil, user: nil, sftp: true)
240
+ opts = { "sftp" => sftp ? true : false }
241
+ opts["host_key_path"] = host_key_path.to_s if host_key_path
242
+ opts["authorized_keys_path"] = authorized_keys_path.to_s if authorized_keys_path
243
+ opts["user"] = user.to_s if user
244
+ SshServer.new(@native.ssh_prepare_server(opts))
245
+ end
246
+ end
247
+ end
@@ -5,5 +5,5 @@ module Microsandbox
5
5
  # the pinned core-crate tag); the patch segment advances for gem-only revisions
6
6
  # that add bindings atop the same core. Must equal the native ext's Cargo crate
7
7
  # version (`Native.version`), enforced by spec/unit/version_spec.rb.
8
- VERSION = "0.5.8"
8
+ VERSION = "0.5.9"
9
9
  end
data/lib/microsandbox.rb CHANGED
@@ -22,6 +22,10 @@ require_relative "microsandbox/streams"
22
22
  require_relative "microsandbox/image"
23
23
  require_relative "microsandbox/volume"
24
24
  require_relative "microsandbox/snapshot"
25
+ require_relative "microsandbox/patch"
26
+ require_relative "microsandbox/network"
27
+ require_relative "microsandbox/agent"
28
+ require_relative "microsandbox/ssh"
25
29
  require_relative "microsandbox/sandbox"
26
30
 
27
31
  # Microsandbox — lightweight microVM sandboxes for Ruby.
data/sig/microsandbox.rbs CHANGED
@@ -140,7 +140,8 @@ module Microsandbox
140
140
  ?user: String?, ?hostname: String?, ?labels: Hash[untyped, untyped]?,
141
141
  ?scripts: Hash[untyped, untyped]?, ?entrypoint: Array[String]?,
142
142
  ?ports: Hash[untyped, untyped]?, ?ports_udp: Hash[untyped, untyped]?,
143
- ?volumes: Hash[untyped, untyped]?, ?network: untyped?, ?from_snapshot: String?,
143
+ ?volumes: Hash[untyped, untyped]?, ?network: untyped?,
144
+ ?patches: Array[Hash[untyped, untyped]]?, ?from_snapshot: String?,
144
145
  ?log_level: (String | Symbol)?, ?quiet_logs: bool, ?security: (String | Symbol)?,
145
146
  ?oci_upper_size: Integer?, ?max_duration: Integer?, ?idle_timeout: Integer?,
146
147
  ?rlimits: Hash[untyped, untyped]?, ?pull_policy: (String | Symbol)?,
@@ -166,7 +167,12 @@ module Microsandbox
166
167
  ?rlimits: Hash[untyped, untyped]?) -> ExecHandle
167
168
  def shell_stream: (String script, ?cwd: String?, ?user: String?, ?env: Hash[untyped, untyped]?,
168
169
  ?timeout: Numeric?, ?tty: bool, ?stdin: String?, ?rlimits: Hash[untyped, untyped]?) -> ExecHandle
170
+ def attach: (String command, ?Array[String] args, ?cwd: String?, ?user: String?,
171
+ ?env: Hash[untyped, untyped]?, ?detach_keys: String?,
172
+ ?rlimits: Hash[untyped, untyped]?) -> Integer
173
+ def attach_shell: () -> Integer
169
174
  def fs: () -> FS
175
+ def ssh: () -> SshOps
170
176
  def metrics: () -> Metrics
171
177
  def logs: (?tail: Integer?, ?since_ms: Numeric?, ?until_ms: Numeric?,
172
178
  ?sources: Array[String | Symbol]?) -> Array[LogEntry]
@@ -321,4 +327,130 @@ module Microsandbox
321
327
  ?with_image: bool, ?plain_tar: bool) -> nil
322
328
  def self.import: (String archive_path, ?dest: String?) -> SnapshotInfo
323
329
  end
330
+
331
+ module Patch
332
+ def self.text: (String path, String content, ?mode: Integer?, ?replace: bool) -> Hash[String, untyped]
333
+ def self.file: (String path, String content, ?mode: Integer?, ?replace: bool) -> Hash[String, untyped]
334
+ def self.append: (String path, String content) -> Hash[String, untyped]
335
+ def self.copy_file: (String src, String dst, ?mode: Integer?, ?replace: bool) -> Hash[String, untyped]
336
+ def self.copy_dir: (String src, String dst, ?replace: bool) -> Hash[String, untyped]
337
+ def self.symlink: (String target, String link, ?replace: bool) -> Hash[String, untyped]
338
+ def self.mkdir: (String path, ?mode: Integer?) -> Hash[String, untyped]
339
+ def self.remove: (String path) -> Hash[String, untyped]
340
+ end
341
+
342
+ module Destination
343
+ def self.any: () -> Hash[String, String]
344
+ def self.ip: (String value) -> Hash[String, String]
345
+ def self.cidr: (String value) -> Hash[String, String]
346
+ def self.domain: (String value) -> Hash[String, String]
347
+ def self.domain_suffix: (String value) -> Hash[String, String]
348
+ def self.group: ((String | Symbol) value) -> Hash[String, String]
349
+ end
350
+
351
+ module Rule
352
+ def self.allow: (?destination: untyped?, ?direction: (String | Symbol),
353
+ ?protocol: (String | Symbol)?, ?protocols: Array[String | Symbol]?,
354
+ ?port: (String | Integer)?, ?ports: Array[String | Integer]?) -> Hash[String, untyped]
355
+ def self.deny: (?destination: untyped?, ?direction: (String | Symbol),
356
+ ?protocol: (String | Symbol)?, ?protocols: Array[String | Symbol]?,
357
+ ?port: (String | Integer)?, ?ports: Array[String | Integer]?) -> Hash[String, untyped]
358
+ end
359
+
360
+ class NetworkPolicy
361
+ PRESET_ALIASES: Hash[String, String]
362
+ def self.public_only: () -> NetworkPolicy
363
+ def self.none: () -> NetworkPolicy
364
+ def self.allow_all: () -> NetworkPolicy
365
+ def self.non_local: () -> NetworkPolicy
366
+ def self.preset: ((String | Symbol) name) -> NetworkPolicy
367
+ def self.custom: (?default_egress: (String | Symbol)?, ?default_ingress: (String | Symbol)?,
368
+ ?rules: Array[Hash[untyped, untyped]], ?deny_domains: Array[String],
369
+ ?deny_domain_suffixes: Array[String]) -> NetworkPolicy
370
+ def self.coerce: (untyped network) -> Hash[String, untyped]
371
+ def initialize: (Hash[String, untyped] wire) -> void
372
+ def to_h: () -> Hash[String, untyped]
373
+ end
374
+
375
+ class AgentFrame
376
+ def initialize: (Hash[String, untyped] data) -> void
377
+ def id: () -> Integer
378
+ def flags: () -> Integer
379
+ def body: () -> String
380
+ def terminal?: () -> bool
381
+ def session_start?: () -> bool
382
+ def shutdown?: () -> bool
383
+ end
384
+
385
+ class AgentStream
386
+ include Enumerable[AgentFrame]
387
+ def id: () -> Integer
388
+ def recv: () -> AgentFrame?
389
+ def each: () { (AgentFrame) -> void } -> self
390
+ | () -> Enumerator[AgentFrame, self]
391
+ def close: () -> nil
392
+ end
393
+
394
+ class AgentClient
395
+ FLAG_TERMINAL: Integer
396
+ FLAG_SESSION_START: Integer
397
+ FLAG_SHUTDOWN: Integer
398
+ def self.connect_sandbox: (String name, ?timeout: Numeric?) ?{ (AgentClient) -> untyped } -> untyped
399
+ def self.connect_path: (String path, ?timeout: Numeric?) ?{ (AgentClient) -> untyped } -> untyped
400
+ def self.socket_path: (String name) -> String
401
+ def initialize: (untyped native) -> void
402
+ def request: (Integer flags, String body) -> AgentFrame
403
+ def stream: (Integer flags, String body) -> AgentStream
404
+ def send_frame: (Integer id, Integer flags, String body) -> nil
405
+ def ready_bytes: () -> String
406
+ def close: () -> nil
407
+ end
408
+
409
+ class SshOutput
410
+ def initialize: (Hash[String, untyped] data) -> void
411
+ def status: () -> Integer
412
+ def success?: () -> bool
413
+ def failure?: () -> bool
414
+ def stdout: () -> String
415
+ def stderr: () -> String
416
+ def stdout_bytes: () -> String
417
+ def stderr_bytes: () -> String
418
+ def to_s: () -> String
419
+ end
420
+
421
+ class SftpClient
422
+ def initialize: (untyped native) -> void
423
+ def read: (String path) -> String
424
+ def read_text: (String path) -> String
425
+ def write: (String path, String data) -> nil
426
+ def mkdir: (String path) -> nil
427
+ def remove_file: (String path) -> nil
428
+ def remove_dir: (String path) -> nil
429
+ def rename: (String old_path, String new_path) -> nil
430
+ def symlink: (String target, String link_path) -> nil
431
+ def real_path: (String path) -> String
432
+ def read_link: (String path) -> String
433
+ def close: () -> nil
434
+ end
435
+
436
+ class SshClient
437
+ def initialize: (untyped native) -> void
438
+ def exec: (String command, ?tty: bool) -> SshOutput
439
+ def attach: (?term: String?, ?detach_keys: String?) -> Integer
440
+ def sftp: () ?{ (SftpClient) -> untyped } -> untyped
441
+ def close: () -> nil
442
+ end
443
+
444
+ class SshServer
445
+ def initialize: (untyped native) -> void
446
+ def serve_connection: () -> nil
447
+ def close: () -> nil
448
+ end
449
+
450
+ class SshOps
451
+ def initialize: (untyped native) -> void
452
+ def open_client: (?user: String, ?term: String?, ?sftp: bool) ?{ (SshClient) -> untyped } -> untyped
453
+ def prepare_server: (?host_key_path: String?, ?authorized_keys_path: String?,
454
+ ?user: String?, ?sftp: bool) -> SshServer
455
+ end
324
456
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: microsandbox-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.8
4
+ version: 0.5.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - ya-luotao
@@ -45,6 +45,7 @@ files:
45
45
  - README.md
46
46
  - ext/microsandbox/Cargo.toml
47
47
  - ext/microsandbox/extconf.rb
48
+ - ext/microsandbox/src/agent.rs
48
49
  - ext/microsandbox/src/conv.rs
49
50
  - ext/microsandbox/src/error.rs
50
51
  - ext/microsandbox/src/exec.rs
@@ -53,9 +54,11 @@ files:
53
54
  - ext/microsandbox/src/runtime.rs
54
55
  - ext/microsandbox/src/sandbox.rs
55
56
  - ext/microsandbox/src/snapshot.rs
57
+ - ext/microsandbox/src/ssh.rs
56
58
  - ext/microsandbox/src/stream.rs
57
59
  - ext/microsandbox/src/volume.rs
58
60
  - lib/microsandbox.rb
61
+ - lib/microsandbox/agent.rb
59
62
  - lib/microsandbox/errors.rb
60
63
  - lib/microsandbox/exec_handle.rb
61
64
  - lib/microsandbox/exec_output.rb
@@ -63,8 +66,11 @@ files:
63
66
  - lib/microsandbox/image.rb
64
67
  - lib/microsandbox/log_entry.rb
65
68
  - lib/microsandbox/metrics.rb
69
+ - lib/microsandbox/network.rb
70
+ - lib/microsandbox/patch.rb
66
71
  - lib/microsandbox/sandbox.rb
67
72
  - lib/microsandbox/snapshot.rb
73
+ - lib/microsandbox/ssh.rb
68
74
  - lib/microsandbox/streams.rb
69
75
  - lib/microsandbox/version.rb
70
76
  - lib/microsandbox/volume.rb