bsdkrun 0.3.2 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14072fa1b965040c136001ee65a8ef384205cbc119ef91658c387e2ed84100fb
4
- data.tar.gz: c3981e13c6a964c084b3a1b54a051801ab373eda107f74dd869e789242c744df
3
+ metadata.gz: e78b89f68872f94abe500dba9bc0d69bd2b8ff0488745bb1f7e07ef2658f8a28
4
+ data.tar.gz: 1effefa03711b90030219d9456edbb6df361591184aa9bb7f01c06e87ad582e2
5
5
  SHA512:
6
- metadata.gz: 5dcbfd7f46c6e412acc0eccc6ccbe639fdffaf875a8ac56e8af0bbac4ec4654804a149f8eff78316ad0ca06cf2338414de320cf6ba05ca8a8ea11d579167c92e
7
- data.tar.gz: 165af797cc3af4b111c9fad1c02f8f1b788d1e900e1d49715e8547b02e8849b768b21ed3cc81d4f922accb073b46f33f7400d7ff854f5868de92869802d3699a
6
+ metadata.gz: ce3f123068f4c08775e7308513b727182ece1ce14737de2cc874ed050be8e19979e82c902703202f72a162222d0b12363a6aefc5b9441eeab34038976ed28a1c
7
+ data.tar.gz: 8eb660e5db46bf1fc617b250f76c55f41f75943f2bc71026994790e812c000ae90aa63384956938e7a5bda406d8f59912ab36ac7f5a5029a615738a1ccb4dca3
data/README.md CHANGED
@@ -9,14 +9,14 @@ dependencies** — just the Ruby standard library (`open3`, `json`, `pathname`).
9
9
  ```ruby
10
10
  require "bsdkrun"
11
11
 
12
- box = Bsdkrun::Sandbox.create(os: "linux", image: "alpine")
12
+ sbx = Bsdkrun::Sandbox.create(os: "linux", image: "alpine")
13
13
 
14
14
  # exec argv directly, with env / stdin / a PTY / a working dir:
15
- puts box.exec(["uname", "-a"]).text
16
- box.exec(["apk", "add", "curl"], throw_on_error: true)
17
- box.run_command("curl", ["-fsSL", "https://example.com"])
15
+ puts sbx.exec(["uname", "-a"]).text
16
+ sbx.exec(["apk", "add", "curl"], throw_on_error: true)
17
+ sbx.run_command("curl", ["-fsSL", "https://example.com"])
18
18
 
19
- box.stop
19
+ sbx.stop
20
20
  ```
21
21
 
22
22
  ## Install
@@ -77,15 +77,34 @@ Bsdkrun::Sandbox.create(os: "kernel", kernel: "netbsd", format: "elf", disk: "ro
77
77
 
78
78
  Every `create` runs the machine **detached** and returns a `Sandbox` handle.
79
79
 
80
+ ### Environment variables
81
+
82
+ `env:` sets the guest environment for the machine's entrypoint. It is merged
83
+ over the image's own config, so a key the image already defines is replaced
84
+ rather than duplicated.
85
+
86
+ ```ruby
87
+ sbx = Bsdkrun::Sandbox.create(
88
+ os: "linux",
89
+ image: "node:22",
90
+ env: { "NODE_ENV" => "production", "PORT" => "3000" },
91
+ command: ["node", "server.js"]
92
+ )
93
+ ```
94
+
95
+ Linux guests only — BSD guests boot their own init, so there is no generated
96
+ init to export into; set those from `exec` after boot. For a single command
97
+ rather than the whole machine, `exec` takes its own `env:`.
98
+
80
99
  ## Running commands
81
100
 
82
101
  `exec` is the primary programmatic entrypoint. No shell parsing — pass an argv
83
102
  array (or a program name plus `args:`).
84
103
 
85
104
  ```ruby
86
- box.exec(["ls", "-la", "/etc"])
105
+ sbx.exec(["ls", "-la", "/etc"])
87
106
 
88
- box.exec("ruby",
107
+ sbx.exec("ruby",
89
108
  args: ["-e", "puts ENV['X']"],
90
109
  env: { "X" => "hi" },
91
110
  cwd: "/app",
@@ -96,7 +115,7 @@ box.exec("ruby",
96
115
  throw_on_error: true) # raise on non-zero exit (default: false)
97
116
 
98
117
  # Vercel-Sandbox-style alias:
99
- result = box.run_command("uname", ["-a"])
118
+ result = sbx.run_command("uname", ["-a"])
100
119
  result.stdout # raw stdout
101
120
  result.text # stdout, trailing newlines trimmed
102
121
  result.exit_code
@@ -111,21 +130,74 @@ The callbacks run as chunks arrive, and the same bytes remain buffered in the
111
130
  returned result. They do not require `tty`; a PTY changes command semantics and
112
131
  may merge stderr into stdout.
113
132
 
133
+ ## Caching
134
+
135
+ `sbx.cache` saves a guest directory under a key and restores it later, so a
136
+ rebuild can pick up where the last one left off. **A miss is not an error** —
137
+ check `restored` rather than rescuing.
138
+
139
+ ```ruby
140
+ key = "deps-#{lock_hash}"
141
+ hit = sbx.cache.restore(key: key, restore_keys: ["deps-"])
142
+ unless hit.restored
143
+ sbx.exec(["npm", "ci"])
144
+ sbx.cache.save("/app/node_modules", key: key, compression: "zstd")
145
+ end
146
+
147
+ Bsdkrun::Caches.ls # every stored entry, newest first
148
+ Bsdkrun::Caches.rm([key]) # or Bsdkrun::Caches.rm(all: true)
149
+ ```
150
+
151
+ `restore_keys` are prefixes tried in order when the exact key misses; within a
152
+ prefix the newest matching entry wins, and `hit.key` says which one was used.
153
+ Formats are `gzip` (default), `zstd`, `estargz` and `none`.
154
+
155
+ Where entries live is host configuration, not an SDK concern: the default is
156
+ this host's disk, and `BSDKRUN_CACHE_BACKEND=s3` + `BSDKRUN_CACHE_S3_*` (or
157
+ `~/.config/bsdkrun/cache.toml`) points them at a bucket instead.
158
+
159
+ ## Files
160
+
161
+ `sbx.fs` reads and writes files in the guest. Parent directories are created
162
+ for you, and everything is byte-exact — `read_file` returns a binary string.
163
+
164
+ ```ruby
165
+ sbx.fs.write_file("/app/main.py", "print('hi')")
166
+ sbx.fs.write_file("/app/logo.png", png_bytes)
167
+
168
+ text = sbx.fs.read_text("/app/out.json")
169
+ bytes = sbx.fs.read_file("/app/logo.png")
170
+
171
+ sbx.fs.upload("./src", "/app/src") # file or directory
172
+ sbx.fs.download("/app/dist", "./dist", recursive: true)
173
+ ```
174
+
175
+ `upload` looks at the local path to decide whether to recurse; `download` cannot
176
+ (the path is in the guest), so say so for a directory. A directory's *contents*
177
+ land in the destination: uploading `./src` to `/app/src` leaves the guest's
178
+ `/app/src` holding what `./src` holds.
179
+
180
+ Failures raise `Bsdkrun::FileTransferFailed`, which carries the offending `path`.
181
+
182
+ > Transfers ride the same in-guest agent as `exec`, so the sandbox must be
183
+ > running. A directory copy also needs `tar` in the guest; single files need
184
+ > only the shell every bootable image already has.
185
+
114
186
  ## Lifecycle & inventory
115
187
 
116
188
  ```ruby
117
- box = Bsdkrun::Sandbox.create(os: "linux", image: "alpine", command: ["sleep", "300"])
118
- same = Bsdkrun::Sandbox.get(box.id) # reconnect (prefix ok)
189
+ sbx = Bsdkrun::Sandbox.create(os: "linux", image: "alpine", command: ["sleep", "300"])
190
+ same = Bsdkrun::Sandbox.get(sbx.id) # reconnect (prefix ok)
119
191
  all = Bsdkrun::Sandbox.list(all: true) # Array<SandboxInfo>
120
192
 
121
- box.status # SandboxInfo | nil
122
- box.running? # true / false
123
- box.logs # console log (String)
124
- box.shell # interactive shell (inherits the terminal)
125
- box.stop # BSD guests clean-poweroff; Linux SIGTERM
126
- box.start # restart in place — resumes its own disk/rootfs (data persists)
127
- box.update(cpus: 4, mem: 2048) # applies on next start
128
- box.remove(force: true)
193
+ sbx.status # SandboxInfo | nil
194
+ sbx.running? # true / false
195
+ sbx.logs # console log (String)
196
+ sbx.shell # interactive shell (inherits the terminal)
197
+ sbx.stop # BSD guests clean-poweroff; Linux SIGTERM
198
+ sbx.start # restart in place — resumes its own disk/rootfs (data persists)
199
+ sbx.update(cpus: 4, mem: 2048) # applies on next start
200
+ sbx.remove(force: true)
129
201
  ```
130
202
 
131
203
  Host-level namespaces:
@@ -147,11 +219,11 @@ Bsdkrun::System.versions("netbsd") # Array<String>
147
219
  Bsdkrun::Sandbox.create(os: "linux", image: "alpine", net: { ports: ["2222:22"] })
148
220
 
149
221
  # agent-managed key-based SSH
150
- box.ssh_setup # install local ~/.ssh/*.pub keys
151
- box.ssh_setup(user: "tsiry", key: "~/.ssh/work.pub")
222
+ sbx.ssh_setup # install local ~/.ssh/*.pub keys
223
+ sbx.ssh_setup(user: "tsiry", key: "~/.ssh/work.pub")
152
224
 
153
225
  # put a guest on your tailnet
154
- box.tailscale_up(authkey: "tskey-auth-...", hostname: "web")
226
+ sbx.tailscale_up(authkey: "tskey-auth-...", hostname: "web")
155
227
  ```
156
228
 
157
229
  ### Global networks — reach machines by name
data/lib/bsdkrun/args.rb CHANGED
@@ -28,6 +28,17 @@ module Bsdkrun
28
28
  # +--name+ flag if a name is set.
29
29
  # @param o [Hash]
30
30
  # @return [Array<String>]
31
+ # +-e K=V+ per entry, sorted by key.
32
+ #
33
+ # A hash has no argv order of its own, so sorting is what makes the command
34
+ # line — and the tests that assert on it — deterministic. The guest sees the
35
+ # same environment either way.
36
+ def env_args(env)
37
+ return [] if env.nil? || env.empty?
38
+
39
+ env.to_h.sort_by { |k, _| k.to_s }.flat_map { |k, v| ["-e", "#{k}=#{v}"] }
40
+ end
41
+
31
42
  def name_args(o)
32
43
  o[:name] ? ["--name", o[:name].to_s] : []
33
44
  end
@@ -81,7 +92,9 @@ module Bsdkrun
81
92
  a.push("--initramfs") if opts[:initramfs]
82
93
  a.push("-v", opts[:volume]) if opts[:volume]
83
94
  Array(opts[:mounts]).each { |m| a.push("--mount", m) }
95
+ Array(opts[:attach_disk]).each { |d| a.push("--attach-disk", d) }
84
96
  a.push("--entrypoint", opts[:entrypoint]) if opts[:entrypoint]
97
+ a.concat(env_args(opts[:env]))
85
98
  a.push("--console", opts[:console]) if opts[:console]
86
99
  a.concat(net_args(opts[:net])).concat(name_args(opts)).concat(vm_args(opts))
87
100
  cmd = opts[:command]
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Bsdkrun
6
+ # A stored cache entry, as +cache ls+ reports it.
7
+ CacheEntry = Struct.new(:key, :path, :compression, :size, :created, :digest, keyword_init: true) do
8
+ def self.from(row)
9
+ new(
10
+ key: row["key"].to_s,
11
+ path: row["path"].to_s,
12
+ compression: row["compression"].to_s,
13
+ size: row["size"].to_i,
14
+ created: row["created"].to_i,
15
+ digest: row["digest"].to_s
16
+ )
17
+ end
18
+ end
19
+
20
+ # What a restore did. A miss is not an error — check +restored+.
21
+ RestoreResult = Struct.new(
22
+ :restored, :requested_key, :key, :path, :size, :compression, :created,
23
+ keyword_init: true
24
+ )
25
+
26
+ # Save and restore guest directories under a key, reached as {Sandbox#cache}.
27
+ #
28
+ # Entries are keyed, so a rebuild can pick up where the last one left off:
29
+ #
30
+ # hit = sbx.cache.restore(key: key, restore_keys: ["deps-"])
31
+ # unless hit.restored
32
+ # sbx.exec(["npm", "ci"])
33
+ # sbx.cache.save("/app/node_modules", key: key)
34
+ # end
35
+ #
36
+ # Where entries live — host disk or S3 — is host configuration, not an SDK
37
+ # concern: set +BSDKRUN_CACHE_BACKEND+ / +BSDKRUN_CACHE_S3_*+, or write
38
+ # +~/.config/bsdkrun/cache.toml+.
39
+ class Cache
40
+ # @param id [String] the machine's id.
41
+ def initialize(id)
42
+ @id = id
43
+ end
44
+
45
+ # Archive the guest directory at +path+ under +key+.
46
+ #
47
+ # @param path [String] absolute path in the guest.
48
+ # @param key [String] key to store under.
49
+ # @param compression [String] gzip (default), zstd, estargz or none.
50
+ # @param force [Boolean] replace an entry that already has this key.
51
+ # @return [CacheEntry]
52
+ def save(path, key:, compression: "gzip", force: false)
53
+ args = ["cache", "save", "#{@id}:#{path}", "--key", key, "--json"]
54
+ args += ["--compression", compression] unless compression == "gzip"
55
+ args << "--force" if force
56
+ CacheEntry.from(json(args, "bsdkrun cache save"))
57
+ end
58
+
59
+ # Restore a stored tree.
60
+ #
61
+ # @param key [String]
62
+ # @param path [String, nil] defaults to where the entry was saved from.
63
+ # @param restore_keys [Array<String>] prefixes tried in order on a miss.
64
+ # @return [RestoreResult]
65
+ def restore(key:, path: nil, restore_keys: [])
66
+ target = path ? "#{@id}:#{path}" : @id
67
+ args = ["cache", "restore", target, "--key", key, "--json"]
68
+ args += ["--restore-keys", *restore_keys] unless restore_keys.empty?
69
+ row = json(args, "bsdkrun cache restore")
70
+ RestoreResult.new(
71
+ restored: !!row["restored"],
72
+ requested_key: row["requested_key"].to_s,
73
+ key: row["key"],
74
+ path: row["path"],
75
+ size: row["size"],
76
+ compression: row["compression"],
77
+ created: row["created"]
78
+ )
79
+ end
80
+
81
+ private
82
+
83
+ def json(args, label)
84
+ out = Process.run!(args, label: label).stdout
85
+ JSON.parse(out.strip.empty? ? "{}" : out)
86
+ end
87
+ end
88
+
89
+ # Host-level cache operations, mirroring {Bsdkrun.volumes}.
90
+ module Caches
91
+ module_function
92
+
93
+ # @return [Array<CacheEntry>] every stored entry, newest first.
94
+ def ls
95
+ out = Process.run!(["cache", "ls", "--json"], label: "bsdkrun cache ls").stdout
96
+ JSON.parse(out.strip.empty? ? "[]" : out).map { |row| CacheEntry.from(row) }
97
+ end
98
+
99
+ # Remove entries by key, or every one with <tt>all: true</tt>.
100
+ # @return [void]
101
+ def rm(keys = [], all: false)
102
+ args = ["cache", "rm"]
103
+ if all
104
+ args << "--all"
105
+ else
106
+ args += Array(keys)
107
+ end
108
+ Process.run!(args, label: "bsdkrun cache rm")
109
+ nil
110
+ end
111
+ end
112
+ end
@@ -293,6 +293,7 @@ module Bsdkrun
293
293
  net: net_input(o[:net]),
294
294
  volume: o[:volume],
295
295
  mounts: o[:mounts] || [],
296
+ attachDisk: o[:attach_disk] || [],
296
297
  env: o[:env] || [],
297
298
  entrypoint: o[:entrypoint],
298
299
  initramfs: o[:initramfs] || false,
@@ -50,6 +50,19 @@ module Bsdkrun
50
50
  end
51
51
  end
52
52
 
53
+ # A guest filesystem operation was refused (see {FileSystem}).
54
+ class FileTransferFailed < Error
55
+ # @return [String] the path that could not be transferred.
56
+ attr_reader :path
57
+
58
+ # @param message [String]
59
+ # @param path [String]
60
+ def initialize(message, path)
61
+ @path = path
62
+ super(message)
63
+ end
64
+ end
65
+
53
66
  # A GraphQL request to a remote +bsdkrund+ daemon failed — a transport
54
67
  # failure, a non-JSON response, or a +body["errors"]+ entry that was not an
55
68
  # auth failure. Mirrors +web/src/lib/graphql.ts+'s +GraphQLError+.
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bsdkrun
4
+ # Files in a running sandbox, reached as {Sandbox#fs}.
5
+ #
6
+ # Every call goes through the guest's exec agent, so the sandbox has to be
7
+ # running — there is no offline write.
8
+ #
9
+ # @example
10
+ # sbx.fs.write_file("/app/main.py", "print('hi')")
11
+ # sbx.fs.read_text("/app/out.json")
12
+ # sbx.fs.upload("./src", "/app/src")
13
+ # sbx.fs.download("/app/dist", "./dist", recursive: true)
14
+ class FileSystem
15
+ # @param id [String] the machine's id.
16
+ def initialize(id)
17
+ @id = id
18
+ end
19
+
20
+ # Write +data+ to +path+ in the guest, creating parent directories.
21
+ #
22
+ # @param path [String] absolute path in the guest.
23
+ # @param data [String] text or binary content.
24
+ # @return [void]
25
+ # @raise [FileTransferFailed]
26
+ def write_file(path, data)
27
+ res = Process.run(["cp", "-", "#{@id}:#{path}"], stdin: data, binary: true)
28
+ check!(res, path)
29
+ nil
30
+ end
31
+
32
+ # Read +path+ from the guest as bytes (ASCII-8BIT).
33
+ #
34
+ # @param path [String]
35
+ # @return [String] binary string.
36
+ # @raise [FileTransferFailed]
37
+ def read_file(path)
38
+ res = Process.run(["cp", "#{@id}:#{path}", "-"], binary: true)
39
+ check!(res, path)
40
+ res.stdout
41
+ end
42
+
43
+ # Read +path+ from the guest and tag it with +encoding+.
44
+ #
45
+ # @param path [String]
46
+ # @param encoding [String]
47
+ # @return [String]
48
+ def read_text(path, encoding: "UTF-8")
49
+ read_file(path).force_encoding(encoding)
50
+ end
51
+
52
+ # Copy a host file or directory into the guest.
53
+ #
54
+ # A directory's *contents* land in +remote_path+, so
55
+ # <tt>upload("./src", "/app/src")</tt> leaves the guest's +/app/src+ holding
56
+ # what +./src+ holds. Whether it recurses is decided by looking at the local
57
+ # path, so callers do not have to say which kind of thing it is.
58
+ #
59
+ # @param local_path [String]
60
+ # @param remote_path [String]
61
+ # @return [void]
62
+ # @raise [FileTransferFailed]
63
+ def upload(local_path, remote_path)
64
+ unless File.exist?(local_path)
65
+ raise FileTransferFailed.new("cannot upload #{local_path}: no such file or directory",
66
+ local_path)
67
+ end
68
+ args = ["cp"]
69
+ args << "-r" if File.directory?(local_path)
70
+ args += [local_path.to_s, "#{@id}:#{remote_path}"]
71
+ check!(Process.run(args), local_path)
72
+ nil
73
+ end
74
+
75
+ # Copy a file or directory out of the guest onto the host.
76
+ #
77
+ # Pass <tt>recursive: true</tt> for a directory; unlike {#upload} it cannot
78
+ # be detected here, because the path lives in the guest and answering would
79
+ # cost an extra round trip.
80
+ #
81
+ # @param remote_path [String]
82
+ # @param local_path [String]
83
+ # @param recursive [Boolean]
84
+ # @return [void]
85
+ # @raise [FileTransferFailed]
86
+ def download(remote_path, local_path, recursive: false)
87
+ args = ["cp"]
88
+ args << "-r" if recursive
89
+ args += ["#{@id}:#{remote_path}", local_path.to_s]
90
+ check!(Process.run(args), remote_path)
91
+ nil
92
+ end
93
+
94
+ private
95
+
96
+ def check!(res, path)
97
+ return if res.exit_code.zero?
98
+
99
+ # The CLI already explains these well; strip its "Error: " prefix.
100
+ text = res.stderr.to_s.strip.sub(/\AError:\s*/, "")
101
+ text = "file transfer failed for #{path}" if text.empty?
102
+ raise FileTransferFailed.new(text, path)
103
+ end
104
+ end
105
+ end
@@ -26,15 +26,23 @@ module Bsdkrun
26
26
  # @param env [Hash] extra environment merged onto the process env.
27
27
  # @param stdin [String, nil] data piped to the child's stdin.
28
28
  # @param log_level [Integer] bsdkrun global log level (0=off .. 5=trace).
29
+ # @param binary [Boolean] keep stdout as bytes (ASCII-8BIT) instead of text.
30
+ # Needed by {FileSystem#read_file}: appending a chunk of arbitrary bytes to
31
+ # a UTF-8 buffer raises Encoding::CompatibilityError, so a PNG read back
32
+ # out of a guest would blow up mid-transfer rather than return.
29
33
  # @return [RawResult]
30
- def run(args, env: {}, stdin: nil, log_level: 0, on_stdout: nil, on_stderr: nil)
34
+ def run(args, env: {}, stdin: nil, log_level: 0, on_stdout: nil, on_stderr: nil, binary: false)
31
35
  bin = Binary.resolve
32
36
  full = ["--log-level", log_level.to_s, *args]
33
37
  merged_env = env.to_h.transform_keys(&:to_s).transform_values(&:to_s)
34
- out = +""
38
+ out = binary ? (+"").b : +""
35
39
  err = +""
36
40
  status = nil
37
41
  Open3.popen3(merged_env, bin, *full) do |child_in, child_out, child_err, wait|
42
+ if binary
43
+ child_in.binmode
44
+ child_out.binmode
45
+ end
38
46
  writer = Thread.new { child_in.write(stdin) if stdin; child_in.close }
39
47
  stdout_reader = Thread.new do
40
48
  while (chunk = child_out.readpartial(8192) rescue nil)
@@ -9,9 +9,9 @@ module Bsdkrun
9
9
  # with {Sandbox.list}.
10
10
  #
11
11
  # @example
12
- # box = Bsdkrun::Sandbox.create(os: "linux", image: "alpine")
13
- # box.exec(["uname", "-a"]).text
14
- # box.stop
12
+ # sbx = Bsdkrun::Sandbox.create(os: "linux", image: "alpine")
13
+ # sbx.exec(["uname", "-a"]).text
14
+ # sbx.stop
15
15
  class Sandbox
16
16
  ID_RE = /\A[0-9a-f]{6,}\z/
17
17
  SSH_PORT_RE = /ssh -p (\d+)/
@@ -32,6 +32,18 @@ module Bsdkrun
32
32
  @ssh_port = ssh_port
33
33
  end
34
34
 
35
+ # Read and write files in the guest.
36
+ # @return [FileSystem]
37
+ def fs
38
+ @fs ||= FileSystem.new(@id)
39
+ end
40
+
41
+ # Save and restore guest directories under a key.
42
+ # @return [Cache]
43
+ def cache
44
+ @cache ||= Cache.new(@id)
45
+ end
46
+
35
47
  class << self
36
48
  # Boot a new microVM and return a handle to it.
37
49
  #
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Bsdkrun
4
4
  # The bsdkrun Ruby SDK version.
5
- VERSION = "0.3.2"
5
+ VERSION = "0.4.0"
6
6
  end
data/lib/bsdkrun.rb CHANGED
@@ -5,6 +5,8 @@ require_relative "bsdkrun/errors"
5
5
  require_relative "bsdkrun/binary"
6
6
  require_relative "bsdkrun/process"
7
7
  require_relative "bsdkrun/args"
8
+ require_relative "bsdkrun/cache"
9
+ require_relative "bsdkrun/filesystem"
8
10
  require_relative "bsdkrun/types"
9
11
  require_relative "bsdkrun/sandbox"
10
12
  require_relative "bsdkrun/images"
@@ -23,9 +25,9 @@ require_relative "bsdkrun/client"
23
25
  # @example
24
26
  # require "bsdkrun"
25
27
  #
26
- # box = Bsdkrun::Sandbox.create(os: "linux", image: "alpine")
27
- # puts box.exec(["uname", "-a"]).text
28
- # box.stop
28
+ # sbx = Bsdkrun::Sandbox.create(os: "linux", image: "alpine")
29
+ # puts sbx.exec(["uname", "-a"]).text
30
+ # sbx.stop
29
31
  module Bsdkrun
30
32
  class << self
31
33
  # Force the SDK to use a specific +bsdkrun+ binary, bypassing discovery.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bsdkrun
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tsiry Sandratraina
@@ -51,8 +51,10 @@ files:
51
51
  - lib/bsdkrun.rb
52
52
  - lib/bsdkrun/args.rb
53
53
  - lib/bsdkrun/binary.rb
54
+ - lib/bsdkrun/cache.rb
54
55
  - lib/bsdkrun/client.rb
55
56
  - lib/bsdkrun/errors.rb
57
+ - lib/bsdkrun/filesystem.rb
56
58
  - lib/bsdkrun/images.rb
57
59
  - lib/bsdkrun/networks.rb
58
60
  - lib/bsdkrun/process.rb