Class: OpenSandbox::Sandboxes

Inherits:
Object
  • Object
show all
Defined in:
lib/open_sandbox/sandboxes.rb

Overview

Manages sandbox lifecycle operations.

All methods return typed value objects (Sandbox, Endpoint, etc.) or raise OpenSandbox::Error subclasses on failure.

Instance Method Summary collapse

Constructor Details

#initialize(http, logger: Logger.new(nil)) ⇒ Sandboxes

Returns a new instance of Sandboxes.



9
10
11
12
# File 'lib/open_sandbox/sandboxes.rb', line 9

def initialize(http, logger: Logger.new(nil))
  @http   = http
  @logger = logger
end

Instance Method Details

#create(image:, entrypoint:, resource_limits: { "cpu" => "500m", "memory" => "512Mi" }, timeout: 300, env: {}, metadata: {}, network_policy: nil, volumes: nil, image_auth: nil, extensions: nil, platform: nil) ⇒ Sandbox

Create a new sandbox.

Parameters:

  • image (String)

    container image URI, e.g. “python:3.11”

  • entrypoint (Array<String>)

    command to run, e.g. [“python”, “/app/main.py”]

  • resource_limits (Hash) (defaults to: { "cpu" => "500m", "memory" => "512Mi" })

    CPU/memory limits, e.g. { cpu: “500m”, memory: “512Mi” }

  • timeout (Integer, nil) (defaults to: 300)

    seconds before auto-terminate (min 60); nil = no auto-terminate

  • env (Hash) (defaults to: {})

    environment variables

  • metadata (Hash) (defaults to: {})

    custom key-value labels

  • network_policy (Hash, nil) (defaults to: nil)

    egress policy

  • volumes (Array<Hash>, nil) (defaults to: nil)

    volume mounts

  • image_auth (Hash, nil) (defaults to: nil)

    registry credentials { username:, password: }

  • extensions (Hash, nil) (defaults to: nil)

    provider-specific parameters (e.g. poolRef)

  • platform (Hash, nil) (defaults to: nil)

    { os: “linux”, arch: “amd64” }

Returns:



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/open_sandbox/sandboxes.rb', line 51

def create(
  image:,
  entrypoint:,
  resource_limits: { "cpu" => "500m", "memory" => "512Mi" },
  timeout: 300,
  env: {},
  metadata: {},
  network_policy: nil,
  volumes: nil,
  image_auth: nil,
  extensions: nil,
  platform: nil
)
  body = {
    image:          build_image_spec(image, image_auth),
    entrypoint:     entrypoint,
    resourceLimits: resource_limits.transform_keys(&:to_s),
    env:            env.transform_keys(&:to_s)
  }

  body[:timeout]       = timeout       if timeout
  body[:metadata]      = .transform_keys(&:to_s) if  && !.empty?
  body[:networkPolicy] = network_policy if network_policy
  body[:volumes]       = volumes        if volumes
  body[:extensions]    = extensions.transform_keys(&:to_s) if extensions
  body[:platform]      = { "os" => platform[:os], "arch" => platform[:arch] } if platform

  data = @http.post("/v1/sandboxes", body: body)
  Sandbox.from_hash(data)
end

#delete(sandbox_id) ⇒ nil

Delete (terminate) a sandbox.

Parameters:

  • sandbox_id (String)

Returns:

  • (nil)


86
87
88
# File 'lib/open_sandbox/sandboxes.rb', line 86

def delete(sandbox_id)
  @http.delete("/v1/sandboxes/#{sandbox_id}")
end

#diagnostics(sandbox_id, tail: 50, event_limit: 20) ⇒ String

Get combined diagnostics summary (inspect + events + logs).

Parameters:

  • sandbox_id (String)
  • tail (Integer) (defaults to: 50)
  • event_limit (Integer) (defaults to: 20)

Returns:

  • (String)


185
186
187
188
189
190
# File 'lib/open_sandbox/sandboxes.rb', line 185

def diagnostics(sandbox_id, tail: 50, event_limit: 20)
  @http.get(
    "/v1/sandboxes/#{sandbox_id}/diagnostics/summary",
    query: { tail: tail, eventLimit: event_limit }
  )
end

#endpoint(sandbox_id, port:, use_server_proxy: false) ⇒ Endpoint

Get public endpoint URL for a service port inside the sandbox.

Parameters:

  • sandbox_id (String)
  • port (Integer)

    port number where the service listens

  • use_server_proxy (Boolean) (defaults to: false)

    return server-proxied URL

Returns:



123
124
125
126
127
128
129
# File 'lib/open_sandbox/sandboxes.rb', line 123

def endpoint(sandbox_id, port:, use_server_proxy: false)
  data = @http.get(
    "/v1/sandboxes/#{sandbox_id}/endpoints/#{port}",
    query: { useServerProxy: use_server_proxy }
  )
  Endpoint.from_hash(data)
end

#events(sandbox_id, limit: 50) ⇒ String

Get events for a sandbox.

Parameters:

  • sandbox_id (String)
  • limit (Integer) (defaults to: 50)

Returns:

  • (String)


175
176
177
# File 'lib/open_sandbox/sandboxes.rb', line 175

def events(sandbox_id, limit: 50)
  @http.get("/v1/sandboxes/#{sandbox_id}/diagnostics/events", query: { limit: limit })
end

#get(sandbox_id) ⇒ Sandbox

Get a sandbox by ID.

Parameters:

  • sandbox_id (String)

Returns:

Raises:



32
33
34
35
# File 'lib/open_sandbox/sandboxes.rb', line 32

def get(sandbox_id)
  data = @http.get("/v1/sandboxes/#{sandbox_id}")
  Sandbox.from_hash(data)
end

#inspect_container(sandbox_id) ⇒ String

Get detailed container inspection info.

Parameters:

  • sandbox_id (String)

Returns:

  • (String)


166
167
168
# File 'lib/open_sandbox/sandboxes.rb', line 166

def inspect_container(sandbox_id)
  @http.get("/v1/sandboxes/#{sandbox_id}/diagnostics/inspect")
end

#list(page: 1, page_size: 20, metadata: {}) ⇒ SandboxList

List all sandboxes with optional filters.

Parameters:

  • page (Integer) (defaults to: 1)

    page number (default 1)

  • page_size (Integer) (defaults to: 20)

    items per page (default 20)

  • metadata (Hash) (defaults to: {})

    filter by metadata key-value pairs

Returns:



20
21
22
23
24
25
# File 'lib/open_sandbox/sandboxes.rb', line 20

def list(page: 1, page_size: 20, metadata: {})
  query = { page: page, pageSize: page_size }
  .each { |k, v| query["metadata.#{k}"] = v }
  data = @http.get("/v1/sandboxes", query: query)
  SandboxList.from_hash(data)
end

#logs(sandbox_id, tail: 100, since: nil) ⇒ String

Get container logs for a sandbox.

Parameters:

  • sandbox_id (String)
  • tail (Integer) (defaults to: 100)

    number of trailing lines

  • since (String, nil) (defaults to: nil)

    duration string, e.g. “10m”, “1h”

Returns:

  • (String)


156
157
158
159
160
# File 'lib/open_sandbox/sandboxes.rb', line 156

def logs(sandbox_id, tail: 100, since: nil)
  query = { tail: tail }
  query[:since] = since if since
  @http.get("/v1/sandboxes/#{sandbox_id}/diagnostics/logs", query: query)
end

#pause(sandbox_id) ⇒ nil

Pause a running sandbox (preserves state).

Parameters:

  • sandbox_id (String)

Returns:

  • (nil)


94
95
96
# File 'lib/open_sandbox/sandboxes.rb', line 94

def pause(sandbox_id)
  @http.post("/v1/sandboxes/#{sandbox_id}/pause")
end

#proxy(sandbox_id, port:, method: :get, path: "/", body: nil, headers: {}) ⇒ HTTParty::Response

Proxy an HTTP request to a service running inside the sandbox.

Parameters:

  • sandbox_id (String)
  • port (Integer)
  • method (Symbol) (defaults to: :get)

    :get, :post, :put, :patch, :delete

  • path (String) (defaults to: "/")

    path within the proxied service (default “/”)

  • body (Hash, nil) (defaults to: nil)

    request body

  • headers (Hash) (defaults to: {})

    additional headers

Returns:

  • (HTTParty::Response)

    raw response



140
141
142
143
144
145
146
# File 'lib/open_sandbox/sandboxes.rb', line 140

def proxy(sandbox_id, port:, method: :get, path: "/", body: nil, headers: {})
  proxy_path = path == "/" || path.empty? \
    ? "/v1/sandboxes/#{sandbox_id}/proxy/#{port}" \
    : "/v1/sandboxes/#{sandbox_id}/proxy/#{port}/#{path.delete_prefix('/')}"

  @http.proxy(method, proxy_path, body: body, headers: headers)
end

#renew_expiration(sandbox_id, expires_at:) ⇒ Time

Renew sandbox expiration time.

Parameters:

  • sandbox_id (String)
  • expires_at (Time)

    new expiration time (must be in the future)

Returns:

  • (Time)

    new expiration time



111
112
113
114
115
# File 'lib/open_sandbox/sandboxes.rb', line 111

def renew_expiration(sandbox_id, expires_at:)
  body = { "expiresAt" => expires_at.utc.iso8601 }
  data = @http.post("/v1/sandboxes/#{sandbox_id}/renew-expiration", body: body)
  Time.parse(data["expiresAt"])
end

#resume(sandbox_id) ⇒ nil

Resume a paused sandbox.

Parameters:

  • sandbox_id (String)

Returns:

  • (nil)


102
103
104
# File 'lib/open_sandbox/sandboxes.rb', line 102

def resume(sandbox_id)
  @http.post("/v1/sandboxes/#{sandbox_id}/resume")
end

#wait_until(sandbox_id, target_state:, timeout: 120, interval: 2) {|Sandbox| ... } ⇒ Sandbox

Wait until sandbox reaches a target state (or fails/terminates).

Parameters:

  • sandbox_id (String)
  • target_state (String)

    e.g. SandboxState::RUNNING

  • timeout (Integer) (defaults to: 120)

    max seconds to wait

  • interval (Numeric) (defaults to: 2)

    polling interval in seconds

Yields:

  • (Sandbox)

    called after each poll (optional)

Returns:

  • (Sandbox)

    when target state reached

Raises:

  • (Error)

    if sandbox fails or timeout exceeded



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/open_sandbox/sandboxes.rb', line 203

def wait_until(sandbox_id, target_state:, timeout: 120, interval: 2)
  deadline = Time.now + timeout
  loop do
    sandbox = get(sandbox_id)
    yield sandbox if block_given?

    return sandbox if sandbox.status.state == target_state

    if sandbox.status.failed? || sandbox.status.terminated?
      raise Error, "Sandbox #{sandbox_id} entered #{sandbox.status.state} state: #{sandbox.status.message}"
    end

    raise Error, "Timed out waiting for sandbox #{sandbox_id} to reach #{target_state}" if Time.now >= deadline

    sleep interval
  end
end