daytona 0.218.0 → 0.220.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: b8e213814357ddef2964bbb51dd2bc1639961337e6ec59e06dba8144d905aa4f
4
- data.tar.gz: a924ea5a93b4e0d0366e4758135bcd96685eb095ba2bfbd32efa414d6e6a17c0
3
+ metadata.gz: 6c916e09a640a2761766f1a78652af5821f19a1a123e819844372d87651ca7fc
4
+ data.tar.gz: 486825b3ac58633a87e865a51ad51cdfc42f8bcd1a50f336b83cd43f90aa2856
5
5
  SHA512:
6
- metadata.gz: 0571d6830e6918b940119843ad602ff2c6bdb3342f84b36b4cd3d0586269e532a21721792e174e995bcdc700e2e8daae27c12f31143d669c5cd592f308631931
7
- data.tar.gz: 570d51ca03353f3b9ea61d4a988c914acf5dce042c872aa82da8243df22045211153e7431a2388c9b4ede46ec064319d2145c5c4de5626cd06518daf06a1e398
6
+ metadata.gz: 83c22f64c8aaaf2522945b4fdd7ee1ac6f8fd6f96657027f0e445428ec1907cce96f0f112caee3213ac653ad88a8bca701b0447ce6851451438dca5b834b0576
7
+ data.tar.gz: 7db19827791d02beea4c549c6f53aaa85748504056027f5c0636caaf86a87c18f1017e9ce0c761e1f644da3b30a39dc988c3f09691dea14ee138fe1b834c17f6
@@ -69,6 +69,9 @@ module Daytona
69
69
  # @return [Integer, nil] Time to live in minutes (0 to disable)
70
70
  attr_accessor :ttl_minutes
71
71
 
72
+ # @return [Integer, nil] Minutes to wait for runner assignment before the API auto-destroys the sandbox
73
+ attr_accessor :queue_timeout
74
+
72
75
  # @return [Boolean, nil] Whether the Sandbox should be ephemeral
73
76
  attr_accessor :ephemeral
74
77
 
@@ -110,6 +113,7 @@ module Daytona
110
113
  # sandbox OTel data is sent to this endpoint instead of the default collector and will not be
111
114
  # available in the Daytona analytics API or dashboard.
112
115
  # @param ttl_minutes [Integer, nil] Time to live in minutes (0 to disable)
116
+ # @param queue_timeout [Integer, nil] Minutes to wait for runner assignment before the API auto-destroys the sandbox
113
117
  # @param ephemeral [Boolean, nil] Whether the Sandbox should be ephemeral
114
118
  # @param spot [Boolean, nil] GPU-only. Whether the Sandbox may be instantly terminated to free GPU
115
119
  # capacity for an on-demand GPU Sandbox
@@ -126,6 +130,7 @@ module Daytona
126
130
  auto_archive_interval: nil,
127
131
  auto_delete_interval: nil,
128
132
  ttl_minutes: nil,
133
+ queue_timeout: nil,
129
134
  volumes: nil,
130
135
  secrets: nil,
131
136
  network_block_all: nil,
@@ -149,6 +154,7 @@ module Daytona
149
154
  @auto_archive_interval = auto_archive_interval
150
155
  @auto_delete_interval = auto_delete_interval
151
156
  @ttl_minutes = ttl_minutes
157
+ @queue_timeout = queue_timeout
152
158
  @volumes = volumes
153
159
  @secrets = secrets
154
160
  @network_block_all = network_block_all
@@ -181,6 +187,7 @@ module Daytona
181
187
  auto_archive_interval:,
182
188
  auto_delete_interval:,
183
189
  ttl_minutes:,
190
+ queue_timeout:,
184
191
  volumes:,
185
192
  secrets:,
186
193
  network_block_all:,
@@ -238,6 +245,7 @@ module Daytona
238
245
  # @param auto_archive_interval [Integer, nil] Auto-archive interval in minutes
239
246
  # @param auto_delete_interval [Integer, nil] Auto-delete interval in minutes
240
247
  # @param ttl_minutes [Integer, nil] Time to live in minutes (0 to disable)
248
+ # @param queue_timeout [Integer, nil] Minutes to wait for runner assignment before the API auto-destroys the sandbox
241
249
  # @param volumes [Array<DaytonaApiClient::SandboxVolume>, nil] List of volumes mounts to attach to the Sandbox
242
250
  # @param secrets [Hash<String, String>, nil] Organization Secrets to expose in the Sandbox, as a
243
251
  # mapping of env var name to existing Secret name
@@ -289,6 +297,7 @@ module Daytona
289
297
  # @param auto_archive_interval [Integer, nil] Auto-archive interval in minutes
290
298
  # @param auto_delete_interval [Integer, nil] Auto-delete interval in minutes
291
299
  # @param ttl_minutes [Integer, nil] Time to live in minutes (0 to disable)
300
+ # @param queue_timeout [Integer, nil] Minutes to wait for runner assignment before the API auto-destroys the sandbox
292
301
  # @param volumes [Array<DaytonaApiClient::SandboxVolume>, nil] List of volumes mounts to attach to the Sandbox
293
302
  # @param secrets [Hash<String, String>, nil] Organization Secrets to expose in the Sandbox, as a
294
303
  # mapping of env var name to existing Secret name
@@ -104,7 +104,7 @@ module Daytona
104
104
  # @param params [Daytona::CreateSandboxFromSnapshotParams, Daytona::CreateSandboxFromImageParams, Nil] Sandbox creation parameters
105
105
  # @return [Daytona::Sandbox] The created sandbox
106
106
  # @raise [Daytona::Sdk::Error] If auto_stop_interval, auto_pause_interval, auto_archive_interval, or ttl_minutes is negative,
107
- # or if auto_stop_interval and auto_pause_interval are both non-zero
107
+ # if queue_timeout is not a positive integer, or if auto_stop_interval and auto_pause_interval are both non-zero
108
108
  def create(params = nil, on_snapshot_create_logs: nil)
109
109
  if params.nil?
110
110
  params = CreateSandboxFromSnapshotParams.new(language: CodeLanguage::PYTHON)
@@ -249,7 +249,7 @@ module Daytona
249
249
  # @param on_snapshot_create_logs [Proc]
250
250
  # @return [Daytona::Sandbox] The created sandbox
251
251
  # @raise [Daytona::Sdk::Error] If auto_stop_interval, auto_pause_interval, auto_archive_interval, or ttl_minutes is negative,
252
- # or if auto_stop_interval and auto_pause_interval are both non-zero
252
+ # if queue_timeout is not a positive integer, or if auto_stop_interval and auto_pause_interval are both non-zero
253
253
  def _create(params, timeout: 60, on_snapshot_create_logs: nil)
254
254
  raise Sdk::Error, 'Timeout must be a non-negative number' if timeout.negative?
255
255
 
@@ -274,6 +274,10 @@ module Daytona
274
274
 
275
275
  raise Sdk::Error, 'ttl_minutes must be a non-negative integer' if params.ttl_minutes&.negative?
276
276
 
277
+ unless params.queue_timeout.nil? || (params.queue_timeout.is_a?(Integer) && params.queue_timeout >= 1)
278
+ raise Sdk::Error, 'queue_timeout must be a positive integer'
279
+ end
280
+
277
281
  labels = params.labels&.dup || {}
278
282
  labels[CODE_TOOLBOX_LANGUAGE_LABEL] = params.language.to_s if params.language
279
283
 
@@ -288,6 +292,7 @@ module Daytona
288
292
  auto_archive_interval: params.auto_archive_interval,
289
293
  auto_delete_interval: params.auto_delete_interval,
290
294
  ttl_minutes: params.ttl_minutes,
295
+ queue_timeout: params.queue_timeout,
291
296
  volumes: params.volumes,
292
297
  secrets: params.secrets&.map { |env_var, secret_name| { env_var.to_s => secret_name.to_s } },
293
298
  network_block_all: params.network_block_all,
@@ -333,13 +338,23 @@ module Daytona
333
338
  response = sandbox_api.get_sandbox(response.id)
334
339
  end
335
340
 
336
- # Get build logs URL from API
337
- build_logs_response = sandbox_api.get_build_logs_url(response.id)
338
- uri = URI.parse("#{build_logs_response.url}?follow=true")
341
+ begin
342
+ build_logs_response = sandbox_api.get_build_logs_url(response.id)
343
+ uri = URI.parse("#{build_logs_response.url}?follow=true")
344
+
345
+ headers = {}
346
+ sandbox_api.api_client.update_params_for_auth!(headers, nil, ['bearer'])
347
+ Util.stream_async(uri:, headers:, on_chunk: on_snapshot_create_logs)
348
+ rescue StandardError => e
349
+ refreshed = begin
350
+ sandbox_api.get_sandbox(response.id)
351
+ rescue StandardError
352
+ raise e
353
+ end
354
+ raise e unless destroyed_by_lifecycle?(refreshed)
339
355
 
340
- headers = {}
341
- sandbox_api.api_client.update_params_for_auth!(headers, nil, ['bearer'])
342
- Util.stream_async(uri:, headers:, on_chunk: on_snapshot_create_logs)
356
+ response = refreshed
357
+ end
343
358
  end
344
359
 
345
360
  sandbox = to_sandbox(sandbox_dto: response)
@@ -395,6 +410,15 @@ module Daytona
395
410
 
396
411
  # @param sandbox_dto [DaytonaApiClient::Sandbox, DaytonaApiClient::SandboxListItem]
397
412
  # @return [Daytona::Sandbox]
413
+ def destroyed_by_lifecycle?(sandbox_dto)
414
+ if sandbox_dto.state == DaytonaApiClient::SandboxState::DESTROYED &&
415
+ (sandbox_dto.queue_timed_out_at || sandbox_dto.spot_evicted_at)
416
+ true
417
+ else
418
+ false
419
+ end
420
+ end
421
+
398
422
  def to_sandbox(sandbox_dto:)
399
423
  Sandbox.new(
400
424
  sandbox_dto:,
@@ -116,6 +116,9 @@ module Daytona
116
116
  # @return [String, nil] When the sandbox was evicted by spot preemption
117
117
  attr_reader :spot_evicted_at
118
118
 
119
+ # @return [String, nil] When the sandbox timed out waiting in the queue for a runner
120
+ attr_reader :queue_timed_out_at
121
+
119
122
  # @return [String, nil] The GPU type assigned to the sandbox
120
123
  attr_reader :gpu_type
121
124
 
@@ -166,6 +169,9 @@ module Daytona
166
169
  # @return [String, nil] When the sandbox will be automatically destroyed (nil if no TTL is set)
167
170
  attr_reader :auto_destroy_at
168
171
 
172
+ # @return [Integer, nil] Minutes the sandbox may wait for runner assignment before the API auto-destroys it
173
+ attr_reader :queue_timeout
174
+
169
175
  # @return [String] The creation timestamp of the sandbox
170
176
  attr_reader :created_at
171
177
 
@@ -1111,6 +1117,7 @@ module Daytona
1111
1117
  @gpu = sandbox_dto.gpu
1112
1118
  @spot = sandbox_dto.spot || false
1113
1119
  @spot_evicted_at = sandbox_dto.spot_evicted_at
1120
+ @queue_timed_out_at = sandbox_dto.queue_timed_out_at
1114
1121
  @gpu_type = sandbox_dto.gpu_type
1115
1122
  @memory = sandbox_dto.memory
1116
1123
  @disk = sandbox_dto.disk
@@ -1122,6 +1129,7 @@ module Daytona
1122
1129
  @auto_archive_interval = sandbox_dto.auto_archive_interval
1123
1130
  @auto_delete_interval = sandbox_dto.auto_delete_interval
1124
1131
  @auto_destroy_at = sandbox_dto.auto_destroy_at
1132
+ @queue_timeout = sandbox_dto.queue_timeout
1125
1133
  @created_at = sandbox_dto.created_at
1126
1134
  @updated_at = sandbox_dto.updated_at
1127
1135
  @last_activity_at = sandbox_dto.last_activity_at
@@ -1245,6 +1253,7 @@ module Daytona
1245
1253
  first_poll = false
1246
1254
 
1247
1255
  safe_refresh ? refresh_data_safe : refresh
1256
+ raise_destroyed_lifecycle_error_if_needed(target_strings)
1248
1257
 
1249
1258
  mutex.synchronize do
1250
1259
  next if result_state
@@ -1270,6 +1279,8 @@ module Daytona
1270
1279
  rescue StandardError
1271
1280
  raise e
1272
1281
  end
1282
+
1283
+ raise_destroyed_lifecycle_error_if_needed(target_strings)
1273
1284
  current = state.to_s
1274
1285
  return if target_strings.include?(current)
1275
1286
 
@@ -1293,6 +1304,26 @@ module Daytona
1293
1304
  rescue StandardError # rubocop:disable Lint/SuppressedException
1294
1305
  end
1295
1306
 
1307
+ def raise_destroyed_lifecycle_error_if_needed(target_strings)
1308
+ return unless state.to_s == DaytonaApiClient::SandboxState::DESTROYED.to_s
1309
+ return if target_strings.include?(DaytonaApiClient::SandboxState::DESTROYED.to_s)
1310
+
1311
+ raise Sdk::QueueTimeoutError, queue_timeout_message if queue_timed_out_at
1312
+ return unless spot_evicted_at
1313
+
1314
+ raise Sdk::SpotEvictedError,
1315
+ "Sandbox #{id} was evicted by spot preemption at #{spot_evicted_at}"
1316
+ end
1317
+
1318
+ def queue_timeout_message
1319
+ waited = if queue_timeout.nil?
1320
+ ''
1321
+ else
1322
+ " #{queue_timeout} #{queue_timeout == 1 ? 'minute' : 'minutes'}"
1323
+ end
1324
+ "Sandbox #{id} was destroyed after waiting#{waited} for a runner (queue timed out at #{queue_timed_out_at})"
1325
+ end
1326
+
1296
1327
  def ensure_subscribed
1297
1328
  @waiter_mutex.synchronize do
1298
1329
  return unless @subscription_manager
@@ -107,6 +107,17 @@ module Daytona
107
107
  # catch the broader connection-failure category also match.
108
108
  class ConnectionTimeoutError < ConnectionError; end
109
109
 
110
+ # ---------------------------------------------------------------------
111
+ # SDK-detected sandbox lifecycle
112
+ # ---------------------------------------------------------------------
113
+
114
+ # Raised when a Sandbox was destroyed by the API because it waited longer
115
+ # than its queue timeout for a runner.
116
+ class QueueTimeoutError < TimeoutError; end
117
+
118
+ # Raised when a Sandbox was destroyed by spot preemption.
119
+ class SpotEvictedError < Error; end
120
+
110
121
  # ---------------------------------------------------------------------
111
122
  # Domain subclasses — each inherits from the HTTP-status parent that
112
123
  # matches its server-side status code.
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Daytona
7
7
  module Sdk
8
- VERSION = '0.218.0'
8
+ VERSION = '0.220.0'
9
9
  end
10
10
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: daytona
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.218.0
4
+ version: 0.220.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daytona Platforms Inc.
@@ -91,42 +91,42 @@ dependencies:
91
91
  requirements:
92
92
  - - '='
93
93
  - !ruby/object:Gem::Version
94
- version: 0.218.0
94
+ version: 0.220.0
95
95
  type: :runtime
96
96
  prerelease: false
97
97
  version_requirements: !ruby/object:Gem::Requirement
98
98
  requirements:
99
99
  - - '='
100
100
  - !ruby/object:Gem::Version
101
- version: 0.218.0
101
+ version: 0.220.0
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: daytona_api_client
104
104
  requirement: !ruby/object:Gem::Requirement
105
105
  requirements:
106
106
  - - '='
107
107
  - !ruby/object:Gem::Version
108
- version: 0.218.0
108
+ version: 0.220.0
109
109
  type: :runtime
110
110
  prerelease: false
111
111
  version_requirements: !ruby/object:Gem::Requirement
112
112
  requirements:
113
113
  - - '='
114
114
  - !ruby/object:Gem::Version
115
- version: 0.218.0
115
+ version: 0.220.0
116
116
  - !ruby/object:Gem::Dependency
117
117
  name: daytona_toolbox_api_client
118
118
  requirement: !ruby/object:Gem::Requirement
119
119
  requirements:
120
120
  - - '='
121
121
  - !ruby/object:Gem::Version
122
- version: 0.218.0
122
+ version: 0.220.0
123
123
  type: :runtime
124
124
  prerelease: false
125
125
  version_requirements: !ruby/object:Gem::Requirement
126
126
  requirements:
127
127
  - - '='
128
128
  - !ruby/object:Gem::Version
129
- version: 0.218.0
129
+ version: 0.220.0
130
130
  - !ruby/object:Gem::Dependency
131
131
  name: dotenv
132
132
  requirement: !ruby/object:Gem::Requirement