microsandbox-rb 0.5.7
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 +7 -0
- data/CHANGELOG.md +94 -0
- data/Cargo.lock +7455 -0
- data/Cargo.toml +16 -0
- data/DESIGN.md +159 -0
- data/LICENSE +201 -0
- data/README.md +328 -0
- data/ext/microsandbox/Cargo.toml +45 -0
- data/ext/microsandbox/extconf.rb +14 -0
- data/ext/microsandbox/src/conv.rs +74 -0
- data/ext/microsandbox/src/error.rs +72 -0
- data/ext/microsandbox/src/exec.rs +158 -0
- data/ext/microsandbox/src/image.rs +114 -0
- data/ext/microsandbox/src/lib.rs +84 -0
- data/ext/microsandbox/src/runtime.rs +92 -0
- data/ext/microsandbox/src/sandbox.rs +812 -0
- data/ext/microsandbox/src/snapshot.rs +158 -0
- data/ext/microsandbox/src/stream.rs +86 -0
- data/ext/microsandbox/src/volume.rs +97 -0
- data/lib/microsandbox/errors.rb +68 -0
- data/lib/microsandbox/exec_handle.rb +154 -0
- data/lib/microsandbox/exec_output.rb +55 -0
- data/lib/microsandbox/fs.rb +172 -0
- data/lib/microsandbox/image.rb +111 -0
- data/lib/microsandbox/log_entry.rb +38 -0
- data/lib/microsandbox/metrics.rb +55 -0
- data/lib/microsandbox/sandbox.rb +461 -0
- data/lib/microsandbox/snapshot.rb +155 -0
- data/lib/microsandbox/streams.rb +54 -0
- data/lib/microsandbox/version.rb +7 -0
- data/lib/microsandbox/volume.rb +79 -0
- data/lib/microsandbox.rb +78 -0
- data/rust-toolchain.toml +5 -0
- data/sig/microsandbox.rbs +321 -0
- metadata +101 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Microsandbox
|
|
4
|
+
# A directory entry returned by {FS#list}.
|
|
5
|
+
class FsEntry
|
|
6
|
+
# @return [String] absolute guest path
|
|
7
|
+
attr_reader :path
|
|
8
|
+
# @return [Symbol] one of :file, :directory, :symlink, :other
|
|
9
|
+
attr_reader :type
|
|
10
|
+
# @return [Integer] size in bytes
|
|
11
|
+
attr_reader :size
|
|
12
|
+
# @return [Integer] POSIX mode bits
|
|
13
|
+
attr_reader :mode
|
|
14
|
+
|
|
15
|
+
def initialize(data)
|
|
16
|
+
@path = data["path"]
|
|
17
|
+
@type = data["type"].to_sym
|
|
18
|
+
@size = data["size"]
|
|
19
|
+
@mode = data["mode"]
|
|
20
|
+
@modified_ms = data["modified_ms"]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [String] the final path component
|
|
24
|
+
def name
|
|
25
|
+
File.basename(@path)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @return [Time, nil] last-modified time, if known
|
|
29
|
+
def modified
|
|
30
|
+
@modified_ms && Time.at(@modified_ms / 1000.0)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def file? = @type == :file
|
|
34
|
+
def directory? = @type == :directory
|
|
35
|
+
def symlink? = @type == :symlink
|
|
36
|
+
|
|
37
|
+
def inspect
|
|
38
|
+
"#<Microsandbox::FsEntry path=#{@path.inspect} type=#{@type} size=#{@size}>"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# File/directory metadata returned by {FS#stat}.
|
|
43
|
+
class FsMetadata
|
|
44
|
+
# @return [Symbol] one of :file, :directory, :symlink, :other
|
|
45
|
+
attr_reader :type
|
|
46
|
+
# @return [Integer] size in bytes
|
|
47
|
+
attr_reader :size
|
|
48
|
+
# @return [Integer] POSIX mode bits
|
|
49
|
+
attr_reader :mode
|
|
50
|
+
|
|
51
|
+
def initialize(data)
|
|
52
|
+
@type = data["type"].to_sym
|
|
53
|
+
@size = data["size"]
|
|
54
|
+
@mode = data["mode"]
|
|
55
|
+
@readonly = data["readonly"]
|
|
56
|
+
@modified_ms = data["modified_ms"]
|
|
57
|
+
@created_ms = data["created_ms"]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def readonly? = @readonly
|
|
61
|
+
def file? = @type == :file
|
|
62
|
+
def directory? = @type == :directory
|
|
63
|
+
def symlink? = @type == :symlink
|
|
64
|
+
|
|
65
|
+
# @return [Time, nil] last-modified time, if known
|
|
66
|
+
def modified
|
|
67
|
+
@modified_ms && Time.at(@modified_ms / 1000.0)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @return [Time, nil] creation time, if known
|
|
71
|
+
def created
|
|
72
|
+
@created_ms && Time.at(@created_ms / 1000.0)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def inspect
|
|
76
|
+
"#<Microsandbox::FsMetadata type=#{@type} size=#{@size} mode=#{format("%o", @mode)}>"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Guest filesystem operations for a running sandbox. Obtain via {Sandbox#fs}.
|
|
81
|
+
# All paths are paths *inside* the guest VM.
|
|
82
|
+
class FS
|
|
83
|
+
def initialize(native)
|
|
84
|
+
@native = native
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Read a file as raw bytes (ASCII-8BIT).
|
|
88
|
+
# @return [String]
|
|
89
|
+
def read(path)
|
|
90
|
+
@native.fs_read(path.to_s)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Read a file as a UTF-8 string.
|
|
94
|
+
# @return [String]
|
|
95
|
+
def read_text(path)
|
|
96
|
+
@native.fs_read_text(path.to_s)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Write data (a String) to a file, creating or truncating it.
|
|
100
|
+
# @return [nil]
|
|
101
|
+
def write(path, data)
|
|
102
|
+
@native.fs_write(path.to_s, data.to_s)
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# List the entries of a directory.
|
|
107
|
+
# @return [Array<FsEntry>]
|
|
108
|
+
def list(path)
|
|
109
|
+
@native.fs_list(path.to_s).map { |entry| FsEntry.new(entry) }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Create a directory (and any missing parents).
|
|
113
|
+
# @return [nil]
|
|
114
|
+
def mkdir(path)
|
|
115
|
+
@native.fs_mkdir(path.to_s)
|
|
116
|
+
nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Remove a single file.
|
|
120
|
+
# @return [nil]
|
|
121
|
+
def remove(path)
|
|
122
|
+
@native.fs_remove(path.to_s)
|
|
123
|
+
nil
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Remove a directory recursively.
|
|
127
|
+
# @return [nil]
|
|
128
|
+
def remove_dir(path)
|
|
129
|
+
@native.fs_remove_dir(path.to_s)
|
|
130
|
+
nil
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Copy a file within the guest.
|
|
134
|
+
# @return [nil]
|
|
135
|
+
def copy(src, dst)
|
|
136
|
+
@native.fs_copy(src.to_s, dst.to_s)
|
|
137
|
+
nil
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Rename/move a file or directory within the guest.
|
|
141
|
+
# @return [nil]
|
|
142
|
+
def rename(src, dst)
|
|
143
|
+
@native.fs_rename(src.to_s, dst.to_s)
|
|
144
|
+
nil
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# @return [Boolean] whether the path exists in the guest
|
|
148
|
+
def exists?(path)
|
|
149
|
+
@native.fs_exists(path.to_s)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Stat a path.
|
|
153
|
+
# @return [FsMetadata]
|
|
154
|
+
def stat(path)
|
|
155
|
+
FsMetadata.new(@native.fs_stat(path.to_s))
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Copy a file from the host into the guest.
|
|
159
|
+
# @return [nil]
|
|
160
|
+
def copy_from_host(host_path, guest_path)
|
|
161
|
+
@native.fs_copy_from_host(host_path.to_s, guest_path.to_s)
|
|
162
|
+
nil
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Copy a file from the guest to the host.
|
|
166
|
+
# @return [nil]
|
|
167
|
+
def copy_to_host(guest_path, host_path)
|
|
168
|
+
@native.fs_copy_to_host(guest_path.to_s, host_path.to_s)
|
|
169
|
+
nil
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Microsandbox
|
|
4
|
+
# Metadata for a cached OCI image, from {Image.get} / {Image.list}.
|
|
5
|
+
class ImageInfo
|
|
6
|
+
attr_reader :reference, :size_bytes, :manifest_digest, :architecture, :os, :layer_count
|
|
7
|
+
|
|
8
|
+
def initialize(data)
|
|
9
|
+
@reference = data["reference"]
|
|
10
|
+
@size_bytes = data["size_bytes"]
|
|
11
|
+
@manifest_digest = data["manifest_digest"]
|
|
12
|
+
@architecture = data["architecture"]
|
|
13
|
+
@os = data["os"]
|
|
14
|
+
@layer_count = data["layer_count"]
|
|
15
|
+
@created_at_ms = data["created_at_ms"]
|
|
16
|
+
@last_used_at_ms = data["last_used_at_ms"]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @return [Time, nil]
|
|
20
|
+
def created_at
|
|
21
|
+
@created_at_ms && Time.at(@created_at_ms / 1000.0)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @return [Time, nil]
|
|
25
|
+
def last_used_at
|
|
26
|
+
@last_used_at_ms && Time.at(@last_used_at_ms / 1000.0)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def inspect
|
|
30
|
+
"#<Microsandbox::ImageInfo reference=#{@reference.inspect} layers=#{@layer_count}>"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Full inspection detail for a cached image, from {Image.inspect}.
|
|
35
|
+
class ImageDetail
|
|
36
|
+
# @return [ImageInfo]
|
|
37
|
+
attr_reader :handle
|
|
38
|
+
# @return [Hash, nil] OCI config (digest, env, cmd, entrypoint, working_dir, user, stop_signal)
|
|
39
|
+
attr_reader :config
|
|
40
|
+
# @return [Array<Hash>] layer descriptors
|
|
41
|
+
attr_reader :layers
|
|
42
|
+
|
|
43
|
+
def initialize(data)
|
|
44
|
+
@handle = ImageInfo.new(data["handle"])
|
|
45
|
+
@config = data["config"]
|
|
46
|
+
@layers = data["layers"] || []
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def reference = @handle.reference
|
|
50
|
+
|
|
51
|
+
def inspect
|
|
52
|
+
"#<Microsandbox::ImageDetail reference=#{@handle.reference.inspect} layers=#{@layers.size}>"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# The result of {Image.prune}.
|
|
57
|
+
class ImagePruneReport
|
|
58
|
+
attr_reader :image_refs_removed, :manifests_removed, :layers_removed,
|
|
59
|
+
:fsmeta_removed, :vmdk_removed, :bytes_reclaimed
|
|
60
|
+
|
|
61
|
+
def initialize(data)
|
|
62
|
+
@image_refs_removed = data["image_refs_removed"]
|
|
63
|
+
@manifests_removed = data["manifests_removed"]
|
|
64
|
+
@layers_removed = data["layers_removed"]
|
|
65
|
+
@fsmeta_removed = data["fsmeta_removed"]
|
|
66
|
+
@vmdk_removed = data["vmdk_removed"]
|
|
67
|
+
@bytes_reclaimed = data["bytes_reclaimed"]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Management of the local OCI image cache. Images are pulled automatically by
|
|
72
|
+
# {Sandbox.create}; this namespace lets you inspect and prune the cache.
|
|
73
|
+
class Image
|
|
74
|
+
class << self
|
|
75
|
+
# All cached images.
|
|
76
|
+
# @return [Array<ImageInfo>]
|
|
77
|
+
def list
|
|
78
|
+
Native::Image.list.map { |info| ImageInfo.new(info) }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Metadata for one cached image.
|
|
82
|
+
# @return [ImageInfo]
|
|
83
|
+
def get(reference)
|
|
84
|
+
ImageInfo.new(Native::Image.get(reference.to_s))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Full inspection detail for a cached image. With no argument this is the
|
|
88
|
+
# normal class `#inspect` (so object display still works).
|
|
89
|
+
# @return [ImageDetail]
|
|
90
|
+
def inspect(reference = nil)
|
|
91
|
+
return super() if reference.nil?
|
|
92
|
+
|
|
93
|
+
ImageDetail.new(Native::Image.inspect(reference.to_s))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Remove a cached image.
|
|
97
|
+
# @param force [Boolean] remove even if referenced
|
|
98
|
+
# @return [nil]
|
|
99
|
+
def remove(reference, force: false)
|
|
100
|
+
Native::Image.remove(reference.to_s, force)
|
|
101
|
+
nil
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Garbage-collect unreferenced images, manifests, and layers.
|
|
105
|
+
# @return [ImagePruneReport]
|
|
106
|
+
def prune
|
|
107
|
+
ImagePruneReport.new(Native::Image.prune)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Microsandbox
|
|
4
|
+
# A single captured log entry, returned by {Sandbox#logs}.
|
|
5
|
+
class LogEntry
|
|
6
|
+
# @return [Symbol] one of :stdout, :stderr, :output, :system
|
|
7
|
+
attr_reader :source
|
|
8
|
+
# @return [Integer, nil] relay-monotonic session id (nil for system markers)
|
|
9
|
+
attr_reader :session_id
|
|
10
|
+
# @return [String] opaque resume cursor token
|
|
11
|
+
attr_reader :cursor
|
|
12
|
+
# @return [String] raw captured bytes (ASCII-8BIT)
|
|
13
|
+
attr_reader :data
|
|
14
|
+
|
|
15
|
+
def initialize(entry)
|
|
16
|
+
@timestamp_ms = entry["timestamp_ms"]
|
|
17
|
+
@source = entry["source"].to_sym
|
|
18
|
+
@session_id = entry["session_id"]
|
|
19
|
+
@cursor = entry["cursor"]
|
|
20
|
+
@data = entry["data"]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [Time] wall-clock capture time
|
|
24
|
+
def timestamp
|
|
25
|
+
Time.at(@timestamp_ms / 1000.0)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @return [String] the captured bytes decoded as UTF-8 (lenient)
|
|
29
|
+
def text
|
|
30
|
+
@data.dup.force_encoding(Encoding::UTF_8)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def inspect
|
|
34
|
+
"#<Microsandbox::LogEntry source=#{@source} session_id=#{@session_id} " \
|
|
35
|
+
"len=#{@data.bytesize}>"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Microsandbox
|
|
4
|
+
# A point-in-time resource-usage snapshot for a sandbox, returned by
|
|
5
|
+
# {Sandbox#metrics}.
|
|
6
|
+
class Metrics
|
|
7
|
+
# @return [Float] CPU usage as a percentage (0.0–100.0 * vCPUs)
|
|
8
|
+
attr_reader :cpu_percent
|
|
9
|
+
# @return [Integer] cumulative vCPU time in nanoseconds
|
|
10
|
+
attr_reader :vcpu_time_ns
|
|
11
|
+
# @return [Integer] memory currently used, in bytes
|
|
12
|
+
attr_reader :memory_bytes
|
|
13
|
+
# @return [Integer, nil] memory available to the guest, in bytes
|
|
14
|
+
attr_reader :memory_available_bytes
|
|
15
|
+
# @return [Integer, nil] host-resident memory for the VM, in bytes
|
|
16
|
+
attr_reader :memory_host_resident_bytes
|
|
17
|
+
# @return [Integer] memory limit, in bytes
|
|
18
|
+
attr_reader :memory_limit_bytes
|
|
19
|
+
# @return [Integer] cumulative bytes read from disk
|
|
20
|
+
attr_reader :disk_read_bytes
|
|
21
|
+
# @return [Integer] cumulative bytes written to disk
|
|
22
|
+
attr_reader :disk_write_bytes
|
|
23
|
+
# @return [Integer] cumulative bytes received over the network
|
|
24
|
+
attr_reader :net_rx_bytes
|
|
25
|
+
# @return [Integer] cumulative bytes transmitted over the network
|
|
26
|
+
attr_reader :net_tx_bytes
|
|
27
|
+
# @return [Float] sandbox uptime in seconds
|
|
28
|
+
attr_reader :uptime_secs
|
|
29
|
+
|
|
30
|
+
def initialize(data)
|
|
31
|
+
@cpu_percent = data["cpu_percent"]
|
|
32
|
+
@vcpu_time_ns = data["vcpu_time_ns"]
|
|
33
|
+
@memory_bytes = data["memory_bytes"]
|
|
34
|
+
@memory_available_bytes = data["memory_available_bytes"]
|
|
35
|
+
@memory_host_resident_bytes = data["memory_host_resident_bytes"]
|
|
36
|
+
@memory_limit_bytes = data["memory_limit_bytes"]
|
|
37
|
+
@disk_read_bytes = data["disk_read_bytes"]
|
|
38
|
+
@disk_write_bytes = data["disk_write_bytes"]
|
|
39
|
+
@net_rx_bytes = data["net_rx_bytes"]
|
|
40
|
+
@net_tx_bytes = data["net_tx_bytes"]
|
|
41
|
+
@uptime_secs = data["uptime_secs"]
|
|
42
|
+
@timestamp_ms = data["timestamp_ms"]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @return [Time] when this snapshot was captured
|
|
46
|
+
def timestamp
|
|
47
|
+
Time.at(@timestamp_ms / 1000.0)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def inspect
|
|
51
|
+
"#<Microsandbox::Metrics cpu=#{@cpu_percent.round(1)}% " \
|
|
52
|
+
"mem=#{@memory_bytes}/#{@memory_limit_bytes}B uptime=#{@uptime_secs.round(1)}s>"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|