microsandbox-rb 0.10.0 → 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.10.0"
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.6"
18
+ RUNTIME_VERSION = "v0.6.7"
19
19
  end
data/lib/microsandbox.rb CHANGED
@@ -27,6 +27,7 @@ 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"
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,6 @@ 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
177
  def ping: () -> PingResult
178
178
  def touch: () -> TouchResult
179
179
  def modify: (?cpus: Integer?, ?max_cpus: Integer?, ?memory: Integer?, ?max_memory: Integer?,
@@ -237,6 +237,7 @@ module Microsandbox
237
237
  ?patches: Array[Hash[untyped, untyped]]?, ?from_snapshot: String?,
238
238
  ?fstype: String?, ?init: (String | Symbol | Hash[untyped, untyped])?, ?ephemeral: bool,
239
239
  ?log_level: (String | Symbol)?, ?quiet_logs: bool, ?security: (String | Symbol)?,
240
+ ?root_disk: (Integer | Hash[untyped, untyped])?,
240
241
  ?oci_upper_size: Integer?, ?max_duration: Integer?, ?idle_timeout: Integer?,
241
242
  ?rlimits: Hash[untyped, untyped]?, ?pull_policy: (String | Symbol)?,
242
243
  ?registry_auth: Hash[untyped, untyped]?, ?registry_insecure: bool,
@@ -386,6 +387,9 @@ module Microsandbox
386
387
  def self.inspect: (?String? reference) -> (ImageDetail | String)
387
388
  def self.remove: (String reference, ?force: bool) -> nil
388
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
389
393
  end
390
394
 
391
395
  class VolumeInfo
@@ -438,6 +442,14 @@ module Microsandbox
438
442
  def labels: () -> Hash[String, String]
439
443
  def size_bytes: () -> Integer?
440
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?
441
453
  def created_at: () -> Time?
442
454
  def open: () -> SnapshotInfo
443
455
  def remove: (?force: bool) -> nil
@@ -447,15 +459,15 @@ module Microsandbox
447
459
  def digest: () -> String
448
460
  def path: () -> String
449
461
  def status: () -> Symbol
450
- def algorithm: () -> String?
451
- def content_digest: () -> String?
462
+ def algorithm: () -> String
463
+ def content_digest: () -> String
452
464
  def verified?: () -> bool
453
- def not_recorded?: () -> bool
454
465
  end
455
466
 
456
467
  class Snapshot
457
- def self.create: (String source_sandbox, ?name: String?, ?path: String?,
458
- ?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
459
471
  def self.open: (String name_or_path) -> SnapshotInfo
460
472
  def self.get: (String name_or_digest) -> SnapshotInfo
461
473
  def self.list: () -> Array[SnapshotInfo]
@@ -463,9 +475,9 @@ module Microsandbox
463
475
  def self.reindex: (?String? dir) -> Integer
464
476
  def self.remove: (String name_or_path, ?force: bool) -> nil
465
477
  def self.verify: (String name_or_path) -> SnapshotVerifyReport
466
- def self.export: (String name_or_path, String out_path, ?with_parents: bool,
467
- ?with_image: bool, ?plain_tar: bool) -> nil
468
- 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
469
481
  end
470
482
 
471
483
  module Patch
@@ -495,14 +507,23 @@ module Microsandbox
495
507
  def self.deny: (?destination: untyped?, ?direction: (String | Symbol),
496
508
  ?protocol: (String | Symbol)?, ?protocols: Array[String | Symbol]?,
497
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]
498
518
  end
499
519
 
500
520
  class NetworkPolicy
521
+ PROFILES: Array[String]
501
522
  PRESET_ALIASES: Hash[String, String]
502
- def self.public_only: () -> NetworkPolicy
523
+ REMOVED_PRESETS: Hash[String, String]
524
+ def self.from_profiles: (*(String | Symbol) profiles) -> NetworkPolicy
503
525
  def self.none: () -> NetworkPolicy
504
526
  def self.allow_all: () -> NetworkPolicy
505
- def self.non_local: () -> NetworkPolicy
506
527
  def self.preset: ((String | Symbol) name) -> NetworkPolicy
507
528
  def self.custom: (?default_egress: (String | Symbol)?, ?default_ingress: (String | Symbol)?,
508
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.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ya-luotao
@@ -71,6 +71,7 @@ files:
71
71
  - lib/microsandbox/modification.rb
72
72
  - lib/microsandbox/network.rb
73
73
  - lib/microsandbox/patch.rb
74
+ - lib/microsandbox/root_disk.rb
74
75
  - lib/microsandbox/sandbox.rb
75
76
  - lib/microsandbox/snapshot.rb
76
77
  - lib/microsandbox/ssh.rb