microsandbox-rb 0.9.2 → 0.11.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.
@@ -2,17 +2,23 @@
2
2
 
3
3
  module Microsandbox
4
4
  # Metadata for a snapshot artifact, returned by {Snapshot.create}/{Snapshot.open}/
5
- # {Snapshot.get}/{Snapshot.list}/{Snapshot.list_dir}/{Snapshot.import}.
5
+ # {Snapshot.get}/{Snapshot.list}/{Snapshot.list_dir}/{Snapshot.load}.
6
6
  #
7
7
  # `digest` and `path` are always present. The artifact-opening paths
8
8
  # (`create`/`open`/`list_dir`, and {SandboxHandle#snapshot}) carry the full
9
- # manifest — `size_bytes`, `image_ref`, `image_manifest_digest`, `format`,
9
+ # descriptor — `size_bytes`, `image_ref`, `image_manifest_digest`, `format`,
10
10
  # `fstype`, `parent_digest`, `created_at`, `source_sandbox`, and `labels`. The
11
- # index paths (`get`/`list`/`import`) populate `name`, `parent_digest`,
12
- # `image_ref`, `format`, `size_bytes`, and `created_at` (manifest-only fields
13
- # such as `fstype`/`source_sandbox`/`labels` are nil/empty there).
11
+ # index paths (`get`/`list`/`load`) populate `name`, `parent_digest`,
12
+ # `image_ref`, `format`, `size_bytes`, `created_at`, and the index-only
13
+ # `locality`/`availability`/`migration_state` columns (descriptor-only fields
14
+ # such as `source_sandbox`/`labels` are nil/empty there).
15
+ #
16
+ # Snapshots come in two state families (see {#state_kind}): `"file"` — a
17
+ # concrete disk payload, where {#format}/{#fstype}/{#size_bytes} are set — and
18
+ # `"checkpoint"` — a manifest-backed state, where those are nil and the
19
+ # `checkpoint_*` fields are set instead.
14
20
  class SnapshotInfo
15
- # @return [String] manifest digest ("sha256:…") — the canonical identity
21
+ # @return [String] descriptor digest ("sha256:…") — the canonical identity
16
22
  attr_reader :digest
17
23
  # @return [String] artifact directory path
18
24
  attr_reader :path
@@ -22,16 +28,30 @@ module Microsandbox
22
28
  attr_reader :parent_digest
23
29
  # @return [String, nil] source OCI image reference
24
30
  attr_reader :image_ref
25
- # @return [String, nil] OCI manifest digest of the pinned image (manifest paths)
31
+ # @return [String, nil] OCI manifest digest of the pinned image (descriptor paths)
26
32
  attr_reader :image_manifest_digest
27
- # @return [String, nil] upper-layer filesystem type, e.g. "ext4" (manifest paths)
33
+ # @return [String, nil] payload filesystem type, e.g. "ext4" (file state only)
28
34
  attr_reader :fstype
29
35
  # @return [String, nil] best-effort source-sandbox name, if recorded
30
36
  attr_reader :source_sandbox
31
37
  # @return [Hash{String=>String}] user labels ({} for index-only entries)
32
38
  attr_reader :labels
33
- # @return [Integer, nil] artifact size in bytes
39
+ # @return [Integer, nil] payload size in bytes (nil for checkpoint state)
34
40
  attr_reader :size_bytes
41
+ # @return [String, nil] stable checkpoint id (checkpoint state only)
42
+ attr_reader :checkpoint_id
43
+ # @return [String, nil] checkpoint-manifest digest (checkpoint state only)
44
+ attr_reader :checkpoint_manifest_digest
45
+ # @return [String, nil] index locality: "embedded" or "linked" (index paths)
46
+ attr_reader :locality
47
+ # @return [String, nil] index availability, normally "ready" (index paths)
48
+ attr_reader :availability
49
+ # @return [String, nil] descriptor-migration state, normally "canonical"
50
+ # ("reverse_complete" after downgrade tooling ran; index paths)
51
+ attr_reader :migration_state
52
+ # @return [String, nil] stable failure code when a v0.6.6→v0.6.7 descriptor
53
+ # migration was blocked (index paths)
54
+ attr_reader :migration_error_code
35
55
 
36
56
  def initialize(data)
37
57
  @digest = data["digest"]
@@ -46,13 +66,30 @@ module Microsandbox
46
66
  @format = data["format"]
47
67
  @size_bytes = data["size_bytes"]
48
68
  @created_at_ms = data["created_at_ms"]
69
+ @scope = data["scope"]
70
+ @state_kind = data["state_kind"]
71
+ @checkpoint_id = data["checkpoint_id"]
72
+ @checkpoint_manifest_digest = data["checkpoint_manifest_digest"]
73
+ @locality = data["locality"]
74
+ @availability = data["availability"]
75
+ @migration_state = data["migration_state"]
76
+ @migration_error_code = data["migration_error_code"]
49
77
  end
50
78
 
51
- # @return [Symbol, nil] disk format (:raw or :qcow2)
79
+ # @return [Symbol, nil] disk format (:raw or :qcow2; nil for checkpoint state)
52
80
  def format
53
81
  @format&.to_sym
54
82
  end
55
83
 
84
+ # @return [Symbol, nil] payload scope — :disk today, :resumable once VM
85
+ # pause/resume lands upstream
86
+ def scope
87
+ @scope&.to_sym
88
+ end
89
+
90
+ # @return [String, nil] state family: "file" or "checkpoint"
91
+ attr_reader :state_kind
92
+
56
93
  # @return [Time, nil]
57
94
  def created_at
58
95
  @created_at_ms && Time.at(@created_at_ms / 1000.0)
@@ -78,17 +115,19 @@ module Microsandbox
78
115
  end
79
116
  end
80
117
 
81
- # The result of {Snapshot.verify}.
118
+ # The result of {Snapshot.verify}. Schema-1 descriptors always record
119
+ # integrity, so a returned report is always `:verified` — an integrity
120
+ # mismatch raises {SnapshotIntegrityError} instead of returning.
82
121
  class SnapshotVerifyReport
83
- # @return [String] manifest digest
122
+ # @return [String] descriptor digest
84
123
  attr_reader :digest
85
124
  # @return [String] artifact directory path
86
125
  attr_reader :path
87
- # @return [Symbol] :not_recorded or :verified
126
+ # @return [Symbol] :verified
88
127
  attr_reader :status
89
- # @return [String, nil] digest algorithm (when :verified)
128
+ # @return [String] digest algorithm
90
129
  attr_reader :algorithm
91
- # @return [String, nil] matched content digest (when :verified)
130
+ # @return [String] matched content digest
92
131
  attr_reader :content_digest
93
132
 
94
133
  def initialize(data)
@@ -101,38 +140,47 @@ module Microsandbox
101
140
 
102
141
  # @return [Boolean] whether content integrity was recorded and matched
103
142
  def verified? = @status == :verified
104
- # @return [Boolean] whether no integrity descriptor was recorded
105
- def not_recorded? = @status == :not_recorded
106
143
  end
107
144
 
108
145
  # Creation and management of sandbox snapshots. A snapshot captures a stopped
109
- # sandbox's upper layer into a portable artifact; boot from it with
146
+ # sandbox's disk state into a portable artifact; boot from it with
110
147
  # `Sandbox.create(from_snapshot: "name-or-digest")`.
148
+ #
149
+ # v0.6.7 descriptor contract: artifacts are identified by their own name and
150
+ # live at `dest_dir/<name>`; the descriptor file is `snapshot.json`. Artifacts
151
+ # written by ≤0.10.x gems (`manifest.json`) are migrated automatically by the
152
+ # runtime on first use — after which older gem versions can no longer read
153
+ # them (downgrade requires the `msb self downgrade` tooling).
111
154
  class Snapshot
112
155
  class << self
113
- # Create a snapshot of a stopped sandbox.
156
+ # Create a snapshot named `name` from a stopped sandbox.
114
157
  #
115
- # @param source_sandbox [String] name of the (stopped) source sandbox
116
- # @param name [String, nil] destination name under the snapshots dir
117
- # @param path [String, nil] explicit destination directory (alternative to name)
158
+ # @param name [String] the snapshot's own name (its identity)
159
+ # @param from_sandbox [String] name of the (stopped) source sandbox
160
+ # @param dest_dir [String, nil] parent directory override the artifact
161
+ # is written at `dest_dir/<name>` (default: the snapshots dir)
118
162
  # @param labels [Hash, nil] user labels
119
163
  # @param force [Boolean] overwrite an existing artifact at the destination
120
- # @param record_integrity [Boolean] compute + record upper-layer integrity
164
+ # @param record_integrity [Boolean] accepted for compatibility; schema-1
165
+ # descriptors always record integrity, so this is a no-op
166
+ # @param resumable [Boolean] request a resumable (memory+device) snapshot;
167
+ # raises {UnsupportedError} until VM pause/resume lands upstream
121
168
  # @return [SnapshotInfo]
122
- def create(source_sandbox, name: nil, path: nil, labels: nil, force: false, record_integrity: false)
123
- opts = {}
124
- opts["name"] = name.to_s if name
125
- opts["path"] = path.to_s if path
169
+ def create(name, from_sandbox:, dest_dir: nil, labels: nil, force: false,
170
+ record_integrity: false, resumable: false)
171
+ opts = {"from_sandbox" => from_sandbox.to_s}
172
+ opts["dest_dir"] = dest_dir.to_s if dest_dir
126
173
  opts["labels"] = stringify(labels) if labels
127
174
  opts["force"] = true if force
128
175
  opts["record_integrity"] = true if record_integrity
129
- SnapshotInfo.new(Native::Snapshot.create(source_sandbox.to_s, opts))
176
+ opts["resumable"] = true if resumable
177
+ SnapshotInfo.new(Native::Snapshot.create(name.to_s, opts))
130
178
  end
131
179
 
132
180
  # Open a snapshot artifact by bare name or path (cheap metadata
133
- # validation; does not read the upper layer). Unlike {get}, this also
181
+ # validation; does not read the payload). Unlike {get}, this also
134
182
  # works for artifacts addressed by path that were never indexed, and it
135
- # returns the full manifest.
183
+ # returns the full descriptor.
136
184
  # @return [SnapshotInfo]
137
185
  def open(name_or_path)
138
186
  SnapshotInfo.new(Native::Snapshot.open(name_or_path.to_s))
@@ -151,7 +199,7 @@ module Microsandbox
151
199
  end
152
200
 
153
201
  # Enumerate snapshot artifacts under a directory by parsing each
154
- # subdirectory's `manifest.json`, without touching the local index — for
202
+ # subdirectory's `snapshot.json`, without touching the local index — for
155
203
  # inspecting external/un-imported collections (e.g. a mounted volume).
156
204
  # @return [Array<SnapshotInfo>]
157
205
  def list_dir(dir)
@@ -175,31 +223,33 @@ module Microsandbox
175
223
  nil
176
224
  end
177
225
 
178
- # Verify a snapshot's recorded upper-layer integrity.
226
+ # Verify a snapshot's recorded payload integrity.
179
227
  # @return [SnapshotVerifyReport]
180
228
  def verify(name_or_path)
181
229
  SnapshotVerifyReport.new(Native::Snapshot.verify(name_or_path.to_s))
182
230
  end
183
231
 
184
232
  # Bundle a snapshot into a `.tar.zst` (or plain `.tar`) archive.
233
+ # Renamed from `export` in 0.11.0, mirroring the v0.6.7 SDKs.
185
234
  # @param with_parents [Boolean] include ancestor snapshots
186
235
  # @param with_image [Boolean] include OCI image artifacts (boots offline)
187
236
  # @param plain_tar [Boolean] write an uncompressed `.tar`
188
237
  # @return [nil]
189
- def export(name_or_path, out_path, with_parents: false, with_image: false, plain_tar: false)
238
+ def save(name_or_path, out_path, with_parents: false, with_image: false, plain_tar: false)
190
239
  opts = {}
191
240
  opts["with_parents"] = true if with_parents
192
241
  opts["with_image"] = true if with_image
193
242
  opts["plain_tar"] = true if plain_tar
194
- Native::Snapshot.export(name_or_path.to_s, out_path.to_s, opts)
243
+ Native::Snapshot.save(name_or_path.to_s, out_path.to_s, opts)
195
244
  nil
196
245
  end
197
246
 
198
- # Unpack a snapshot archive into the snapshots dir.
247
+ # Unpack a snapshot archive into the snapshots dir. Renamed from
248
+ # `import` in 0.11.0, mirroring the v0.6.7 SDKs.
199
249
  # @param dest [String, nil] explicit destination directory
200
250
  # @return [SnapshotInfo]
201
- def import(archive_path, dest: nil)
202
- SnapshotInfo.new(Native::Snapshot.import(archive_path.to_s, dest&.to_s))
251
+ def load(archive_path, dest: nil)
252
+ SnapshotInfo.new(Native::Snapshot.load(archive_path.to_s, dest&.to_s))
203
253
  end
204
254
 
205
255
  private
@@ -8,12 +8,12 @@ module Microsandbox
8
8
  # Versioning section of the README for the full gem-to-runtime map. Must equal
9
9
  # the native ext's Cargo crate version (`Native.version`), enforced by
10
10
  # spec/unit/version_spec.rb.
11
- VERSION = "0.9.2"
11
+ VERSION = "0.11.0"
12
12
 
13
13
  # The upstream microsandbox runtime release this gem build embeds — the `tag`
14
14
  # pinned on the `microsandbox`/`microsandbox-network` git deps in
15
15
  # ext/microsandbox/Cargo.toml. Exposed at runtime as
16
16
  # {Microsandbox.runtime_version}. spec/unit/version_spec.rb asserts it stays in
17
17
  # sync with the Cargo tag so it can't silently drift out of date.
18
- RUNTIME_VERSION = "v0.6.3"
18
+ RUNTIME_VERSION = "v0.6.7"
19
19
  end
data/lib/microsandbox.rb CHANGED
@@ -27,9 +27,11 @@ require_relative "microsandbox/image"
27
27
  require_relative "microsandbox/volume"
28
28
  require_relative "microsandbox/snapshot"
29
29
  require_relative "microsandbox/patch"
30
+ require_relative "microsandbox/root_disk"
30
31
  require_relative "microsandbox/network"
31
32
  require_relative "microsandbox/agent"
32
33
  require_relative "microsandbox/ssh"
34
+ require_relative "microsandbox/modification"
33
35
  require_relative "microsandbox/sandbox"
34
36
 
35
37
  # Microsandbox — lightweight microVM sandboxes for Ruby.
data/sig/microsandbox.rbs CHANGED
@@ -52,6 +52,7 @@ module Microsandbox
52
52
  class SnapshotSandboxRunningError < Error end
53
53
  class SnapshotImageMissingError < Error end
54
54
  class SnapshotIntegrityError < Error end
55
+ class SnapshotMigrationError < Error end
55
56
  class NetworkPolicyError < Error end
56
57
  class SecretViolationError < Error end
57
58
  class TlsError < Error end
@@ -173,7 +174,14 @@ module Microsandbox
173
174
  def config_json: () -> String
174
175
  def config: () -> Hash[String, untyped]
175
176
  def snapshot: (String name) -> SnapshotInfo
176
- def snapshot_to: (String path) -> SnapshotInfo
177
+ def ping: () -> PingResult
178
+ def touch: () -> TouchResult
179
+ def modify: (?cpus: Integer?, ?max_cpus: Integer?, ?memory: Integer?, ?max_memory: Integer?,
180
+ ?env: Hash[untyped, untyped]?, ?remove_env: Array[String]?,
181
+ ?labels: Hash[untyped, untyped]?, ?remove_labels: Array[String]?,
182
+ ?workdir: String?, ?secrets: Hash[untyped, untyped]?,
183
+ ?remove_secrets: Array[String]?, ?policy: (Symbol | String)?,
184
+ ?dry_run: bool) -> ModificationPlan
177
185
  end
178
186
 
179
187
  # Deprecated alias for SandboxHandle (was a read-only metadata type pre-v0.5.8).
@@ -190,8 +198,34 @@ module Microsandbox
190
198
  def observed_at: () -> Time
191
199
  end
192
200
 
201
+ class PingResult
202
+ def initialize: (Hash[String, untyped] data) -> void
203
+ def name: () -> String
204
+ def latency: () -> Float
205
+ def latency_ms: () -> Float
206
+ end
207
+
208
+ class TouchResult
209
+ def initialize: (Hash[String, untyped] data) -> void
210
+ def name: () -> String
211
+ def activity_seq: () -> Integer
212
+ end
213
+
214
+ class ModificationPlan
215
+ def initialize: (Hash[String, untyped] data) -> void
216
+ def sandbox: () -> String
217
+ def status: () -> String
218
+ def policy: () -> Symbol
219
+ def applied?: () -> bool
220
+ def changes: () -> Array[Hash[Symbol, untyped]]
221
+ def conflicts: () -> Array[Hash[Symbol, untyped]]
222
+ def warnings: () -> Array[Hash[Symbol, untyped]]
223
+ def resize_status: () -> Array[Hash[Symbol, untyped]]
224
+ end
225
+
193
226
  class Sandbox
194
- def self.create: (String name, ?image: String?, ?cpus: Integer?, ?memory: Integer?,
227
+ def self.create: (String name, ?image: String?, ?cpus: Integer?, ?max_cpus: Integer?,
228
+ ?memory: Integer?, ?max_memory: Integer?,
195
229
  ?env: Hash[untyped, untyped]?, ?workdir: String?, ?shell: String?,
196
230
  ?user: String?, ?hostname: String?, ?labels: Hash[untyped, untyped]?,
197
231
  ?scripts: Hash[untyped, untyped]?, ?entrypoint: Array[String]?,
@@ -203,6 +237,7 @@ module Microsandbox
203
237
  ?patches: Array[Hash[untyped, untyped]]?, ?from_snapshot: String?,
204
238
  ?fstype: String?, ?init: (String | Symbol | Hash[untyped, untyped])?, ?ephemeral: bool,
205
239
  ?log_level: (String | Symbol)?, ?quiet_logs: bool, ?security: (String | Symbol)?,
240
+ ?root_disk: (Integer | Hash[untyped, untyped])?,
206
241
  ?oci_upper_size: Integer?, ?max_duration: Integer?, ?idle_timeout: Integer?,
207
242
  ?rlimits: Hash[untyped, untyped]?, ?pull_policy: (String | Symbol)?,
208
243
  ?registry_auth: Hash[untyped, untyped]?, ?registry_insecure: bool,
@@ -238,6 +273,14 @@ module Microsandbox
238
273
  def fs: () -> FS
239
274
  def ssh: () -> SshOps
240
275
  def metrics: () -> Metrics
276
+ def ping: () -> PingResult
277
+ def touch: () -> TouchResult
278
+ def modify: (?cpus: Integer?, ?max_cpus: Integer?, ?memory: Integer?, ?max_memory: Integer?,
279
+ ?env: Hash[untyped, untyped]?, ?remove_env: Array[String]?,
280
+ ?labels: Hash[untyped, untyped]?, ?remove_labels: Array[String]?,
281
+ ?workdir: String?, ?secrets: Hash[untyped, untyped]?,
282
+ ?remove_secrets: Array[String]?, ?policy: (Symbol | String)?,
283
+ ?dry_run: bool) -> ModificationPlan
241
284
  def logs: (?tail: Integer?, ?since_ms: Numeric?, ?until_ms: Numeric?,
242
285
  ?sources: Array[String | Symbol]?) -> Array[LogEntry]
243
286
  def metrics_stream: (?interval: Numeric) -> MetricsStream
@@ -344,6 +387,9 @@ module Microsandbox
344
387
  def self.inspect: (?String? reference) -> (ImageDetail | String)
345
388
  def self.remove: (String reference, ?force: bool) -> nil
346
389
  def self.prune: () -> ImagePruneReport
390
+ def self.load: (String input_path, ?tag: (String | Array[String])?) -> Array[ImageInfo]
391
+ def self.save: ((String | Array[String]) reference, output_path: String,
392
+ ?format: (String | Symbol)) -> nil
347
393
  end
348
394
 
349
395
  class VolumeInfo
@@ -396,6 +442,14 @@ module Microsandbox
396
442
  def labels: () -> Hash[String, String]
397
443
  def size_bytes: () -> Integer?
398
444
  def format: () -> Symbol?
445
+ def scope: () -> Symbol?
446
+ def state_kind: () -> String?
447
+ def checkpoint_id: () -> String?
448
+ def checkpoint_manifest_digest: () -> String?
449
+ def locality: () -> String?
450
+ def availability: () -> String?
451
+ def migration_state: () -> String?
452
+ def migration_error_code: () -> String?
399
453
  def created_at: () -> Time?
400
454
  def open: () -> SnapshotInfo
401
455
  def remove: (?force: bool) -> nil
@@ -405,15 +459,15 @@ module Microsandbox
405
459
  def digest: () -> String
406
460
  def path: () -> String
407
461
  def status: () -> Symbol
408
- def algorithm: () -> String?
409
- def content_digest: () -> String?
462
+ def algorithm: () -> String
463
+ def content_digest: () -> String
410
464
  def verified?: () -> bool
411
- def not_recorded?: () -> bool
412
465
  end
413
466
 
414
467
  class Snapshot
415
- def self.create: (String source_sandbox, ?name: String?, ?path: String?,
416
- ?labels: Hash[untyped, untyped]?, ?force: bool, ?record_integrity: bool) -> SnapshotInfo
468
+ def self.create: (String name, from_sandbox: String, ?dest_dir: String?,
469
+ ?labels: Hash[untyped, untyped]?, ?force: bool, ?record_integrity: bool,
470
+ ?resumable: bool) -> SnapshotInfo
417
471
  def self.open: (String name_or_path) -> SnapshotInfo
418
472
  def self.get: (String name_or_digest) -> SnapshotInfo
419
473
  def self.list: () -> Array[SnapshotInfo]
@@ -421,9 +475,9 @@ module Microsandbox
421
475
  def self.reindex: (?String? dir) -> Integer
422
476
  def self.remove: (String name_or_path, ?force: bool) -> nil
423
477
  def self.verify: (String name_or_path) -> SnapshotVerifyReport
424
- def self.export: (String name_or_path, String out_path, ?with_parents: bool,
425
- ?with_image: bool, ?plain_tar: bool) -> nil
426
- def self.import: (String archive_path, ?dest: String?) -> SnapshotInfo
478
+ def self.save: (String name_or_path, String out_path, ?with_parents: bool,
479
+ ?with_image: bool, ?plain_tar: bool) -> nil
480
+ def self.load: (String archive_path, ?dest: String?) -> SnapshotInfo
427
481
  end
428
482
 
429
483
  module Patch
@@ -453,14 +507,23 @@ module Microsandbox
453
507
  def self.deny: (?destination: untyped?, ?direction: (String | Symbol),
454
508
  ?protocol: (String | Symbol)?, ?protocols: Array[String | Symbol]?,
455
509
  ?port: (String | Integer)?, ?ports: Array[String | Integer]?) -> Hash[String, untyped]
510
+ def self.allow_dns: () -> Hash[String, untyped]
511
+ def self.deny_dns: () -> Hash[String, untyped]
512
+ end
513
+
514
+ module RootDisk
515
+ def self.managed: (?Integer? size_mib) -> Hash[String, untyped]
516
+ def self.tmpfs: (?Integer? size_mib) -> Hash[String, untyped]
517
+ def self.disk: (String path, ?format: (String | Symbol)?, ?fstype: String?) -> Hash[String, untyped]
456
518
  end
457
519
 
458
520
  class NetworkPolicy
521
+ PROFILES: Array[String]
459
522
  PRESET_ALIASES: Hash[String, String]
460
- def self.public_only: () -> NetworkPolicy
523
+ REMOVED_PRESETS: Hash[String, String]
524
+ def self.from_profiles: (*(String | Symbol) profiles) -> NetworkPolicy
461
525
  def self.none: () -> NetworkPolicy
462
526
  def self.allow_all: () -> NetworkPolicy
463
- def self.non_local: () -> NetworkPolicy
464
527
  def self.preset: ((String | Symbol) name) -> NetworkPolicy
465
528
  def self.custom: (?default_egress: (String | Symbol)?, ?default_ingress: (String | Symbol)?,
466
529
  ?rules: Array[Hash[untyped, untyped]], ?deny_domains: Array[String],
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.9.2
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ya-luotao
@@ -68,8 +68,10 @@ files:
68
68
  - lib/microsandbox/image.rb
69
69
  - lib/microsandbox/log_entry.rb
70
70
  - lib/microsandbox/metrics.rb
71
+ - lib/microsandbox/modification.rb
71
72
  - lib/microsandbox/network.rb
72
73
  - lib/microsandbox/patch.rb
74
+ - lib/microsandbox/root_disk.rb
73
75
  - lib/microsandbox/sandbox.rb
74
76
  - lib/microsandbox/snapshot.rb
75
77
  - lib/microsandbox/ssh.rb