daytona 0.198.0 → 0.200.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 +4 -4
- data/lib/daytona/common/daytona.rb +9 -0
- data/lib/daytona/common/socketio_client.rb +77 -6
- data/lib/daytona/daytona.rb +5 -2
- data/lib/daytona/sandbox.rb +18 -0
- data/lib/daytona/sdk/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: de70d9e56ce97468d9ef3bcf8c7dbe0b70f74ee7e64a7ac1d0e8642857a3c257
|
|
4
|
+
data.tar.gz: 3cec447b03a65f42bcd84c49e719cd8771a1404f496384afd5e797b429e47fb1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 91c94980be6003996bbbf4b447b170e9165cbc3a47e78c47f91886f28accd5f7b083b04903fc59dede9bbe56ccdcaf9957da73e07202e762ca928203274b2c8b
|
|
7
|
+
data.tar.gz: db39e610c03a011d4c2468475393ca7b7bd17cc1087dbb0edd46c9fc6959da18e0ca61d861485ca0b27faa8ccb4061f631ec44c97d90d8e6bea7db75317db4e3
|
|
@@ -56,6 +56,9 @@ module Daytona
|
|
|
56
56
|
# @return [String, nil] Comma-separated list of allowed domains for the Sandbox
|
|
57
57
|
attr_accessor :domain_allow_list
|
|
58
58
|
|
|
59
|
+
# @return [Integer, nil] Time to live in minutes (0 to disable)
|
|
60
|
+
attr_accessor :ttl_minutes
|
|
61
|
+
|
|
59
62
|
# @return [Boolean, nil] Whether the Sandbox should be ephemeral
|
|
60
63
|
attr_accessor :ephemeral
|
|
61
64
|
|
|
@@ -83,6 +86,7 @@ module Daytona
|
|
|
83
86
|
# @param network_block_all [Boolean, nil] Whether to block all network access for the Sandbox
|
|
84
87
|
# @param network_allow_list [String, nil] Comma-separated list of allowed CIDR network addresses for the Sandbox
|
|
85
88
|
# @param domain_allow_list [String, nil] Comma-separated list of allowed domains for the Sandbox
|
|
89
|
+
# @param ttl_minutes [Integer, nil] Time to live in minutes (0 to disable)
|
|
86
90
|
# @param ephemeral [Boolean, nil] Whether the Sandbox should be ephemeral
|
|
87
91
|
# @param linked_sandbox [String, nil] ID or name of an existing Sandbox to link the new Sandbox to
|
|
88
92
|
def initialize( # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
|
|
@@ -96,6 +100,7 @@ module Daytona
|
|
|
96
100
|
auto_pause_interval: nil,
|
|
97
101
|
auto_archive_interval: nil,
|
|
98
102
|
auto_delete_interval: nil,
|
|
103
|
+
ttl_minutes: nil,
|
|
99
104
|
volumes: nil,
|
|
100
105
|
secrets: nil,
|
|
101
106
|
network_block_all: nil,
|
|
@@ -114,6 +119,7 @@ module Daytona
|
|
|
114
119
|
@auto_pause_interval = auto_pause_interval
|
|
115
120
|
@auto_archive_interval = auto_archive_interval
|
|
116
121
|
@auto_delete_interval = auto_delete_interval
|
|
122
|
+
@ttl_minutes = ttl_minutes
|
|
117
123
|
@volumes = volumes
|
|
118
124
|
@secrets = secrets
|
|
119
125
|
@network_block_all = network_block_all
|
|
@@ -141,6 +147,7 @@ module Daytona
|
|
|
141
147
|
auto_pause_interval:,
|
|
142
148
|
auto_archive_interval:,
|
|
143
149
|
auto_delete_interval:,
|
|
150
|
+
ttl_minutes:,
|
|
144
151
|
volumes:,
|
|
145
152
|
secrets:,
|
|
146
153
|
network_block_all:,
|
|
@@ -193,6 +200,7 @@ module Daytona
|
|
|
193
200
|
# @param auto_pause_interval [Integer, nil] Auto-pause interval in minutes
|
|
194
201
|
# @param auto_archive_interval [Integer, nil] Auto-archive interval in minutes
|
|
195
202
|
# @param auto_delete_interval [Integer, nil] Auto-delete interval in minutes
|
|
203
|
+
# @param ttl_minutes [Integer, nil] Time to live in minutes (0 to disable)
|
|
196
204
|
# @param volumes [Array<DaytonaApiClient::SandboxVolume>, nil] List of volumes mounts to attach to the Sandbox
|
|
197
205
|
# @param secrets [Hash<String, String>, nil] Organization Secrets to expose in the Sandbox, as a
|
|
198
206
|
# mapping of env var name to existing Secret name
|
|
@@ -235,6 +243,7 @@ module Daytona
|
|
|
235
243
|
# @param auto_pause_interval [Integer, nil] Auto-pause interval in minutes
|
|
236
244
|
# @param auto_archive_interval [Integer, nil] Auto-archive interval in minutes
|
|
237
245
|
# @param auto_delete_interval [Integer, nil] Auto-delete interval in minutes
|
|
246
|
+
# @param ttl_minutes [Integer, nil] Time to live in minutes (0 to disable)
|
|
238
247
|
# @param volumes [Array<DaytonaApiClient::SandboxVolume>, nil] List of volumes mounts to attach to the Sandbox
|
|
239
248
|
# @param secrets [Hash<String, String>, nil] Organization Secrets to expose in the Sandbox, as a
|
|
240
249
|
# mapping of env var name to existing Secret name
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
require 'timeout'
|
|
7
7
|
|
|
8
|
+
require 'openssl'
|
|
8
9
|
require 'websocket-client-simple'
|
|
9
10
|
require 'json'
|
|
10
11
|
require 'uri'
|
|
@@ -52,6 +53,8 @@ module Daytona
|
|
|
52
53
|
@connected = false
|
|
53
54
|
@mutex = Mutex.new
|
|
54
55
|
@write_mutex = Mutex.new
|
|
56
|
+
@verify_mutex = Mutex.new
|
|
57
|
+
@tls_verified = nil
|
|
55
58
|
@health_thread = nil
|
|
56
59
|
@ping_interval = 25
|
|
57
60
|
@ping_timeout = 20
|
|
@@ -67,13 +70,24 @@ module Daytona
|
|
|
67
70
|
ws_url = build_ws_url
|
|
68
71
|
connected_queue = Queue.new
|
|
69
72
|
|
|
73
|
+
# Reset per-connection TLS state so a reconnect re-verifies its new socket
|
|
74
|
+
# instead of reusing a cached result from the previous connection.
|
|
75
|
+
@verify_mutex.synchronize { @tls_verified = nil }
|
|
76
|
+
|
|
70
77
|
# Capture self because websocket-client-simple uses instance_exec for callbacks
|
|
71
78
|
client = self
|
|
72
79
|
|
|
73
80
|
# Register callbacks inside the connect block so they are installed before the
|
|
74
81
|
# socket dials — otherwise the Engine.IO handshake can arrive before :message
|
|
75
82
|
# is bound, stalling the connection into a polling fallback/timeout.
|
|
76
|
-
|
|
83
|
+
#
|
|
84
|
+
# verify_mode is passed explicitly: websocket-client-simple builds its own
|
|
85
|
+
# SSLContext and defaults to no certificate verification (VERIFY_NONE).
|
|
86
|
+
@ws = WebSocket::Client::Simple.connect(ws_url, verify_mode: OpenSSL::SSL::VERIFY_PEER) do |ws|
|
|
87
|
+
# Capture the client before its reader thread starts so #verify_tls_peer!
|
|
88
|
+
# can reach the SSL socket even when a frame arrives before .connect returns.
|
|
89
|
+
@ws = ws
|
|
90
|
+
|
|
77
91
|
ws.on :message do |msg|
|
|
78
92
|
client.send(:handle_raw_message, msg.data.to_s, connected_queue)
|
|
79
93
|
end
|
|
@@ -104,15 +118,26 @@ module Daytona
|
|
|
104
118
|
end
|
|
105
119
|
end
|
|
106
120
|
|
|
107
|
-
# Wait for connection with timeout
|
|
108
121
|
result = nil
|
|
109
122
|
begin
|
|
110
|
-
Timeout.timeout(@connect_timeout)
|
|
123
|
+
Timeout.timeout(@connect_timeout) do
|
|
124
|
+
ensure_tls_verified!(connected_queue)
|
|
125
|
+
result = connected_queue.pop
|
|
126
|
+
if result == :open
|
|
127
|
+
send_connect_auth
|
|
128
|
+
result = connected_queue.pop
|
|
129
|
+
end
|
|
130
|
+
end
|
|
111
131
|
rescue Timeout::Error
|
|
112
132
|
close
|
|
113
133
|
raise "WebSocket connection timed out after #{@connect_timeout}s"
|
|
114
134
|
end
|
|
115
135
|
|
|
136
|
+
if result.is_a?(OpenSSL::SSL::SSLError)
|
|
137
|
+
close
|
|
138
|
+
raise result
|
|
139
|
+
end
|
|
140
|
+
|
|
116
141
|
if result != :connected
|
|
117
142
|
close
|
|
118
143
|
raise "WebSocket connection failed: #{result}"
|
|
@@ -141,6 +166,46 @@ module Daytona
|
|
|
141
166
|
|
|
142
167
|
private
|
|
143
168
|
|
|
169
|
+
# Verify the TLS peer's hostname exactly once, before any inbound frame is
|
|
170
|
+
# processed and before the auth frame is sent. websocket-client-simple
|
|
171
|
+
# verifies the certificate chain (VERIFY_PEER) during the handshake but never
|
|
172
|
+
# checks that the certificate matches the host, and its reader thread starts
|
|
173
|
+
# before #connect regains control — so a certificate that is valid for the
|
|
174
|
+
# wrong host could otherwise inject Socket.IO events (and receive the CONNECT
|
|
175
|
+
# auth frame) before verification runs. Gating both the reader path and the
|
|
176
|
+
# connect path guarantees no frame reaches the dispatcher, and no credentials
|
|
177
|
+
# are sent, until the peer is verified.
|
|
178
|
+
# @return [Boolean] true when verified (or plaintext ws://, no TLS peer to
|
|
179
|
+
# check); false on failure, after signalling the connecting thread.
|
|
180
|
+
def ensure_tls_verified!(connected_queue)
|
|
181
|
+
@verify_mutex.synchronize do
|
|
182
|
+
return @tls_verified unless @tls_verified.nil?
|
|
183
|
+
|
|
184
|
+
begin
|
|
185
|
+
verify_tls_peer!
|
|
186
|
+
@tls_verified = true
|
|
187
|
+
rescue OpenSSL::SSL::SSLError => e
|
|
188
|
+
@tls_verified = false
|
|
189
|
+
@mutex.synchronize { @connected = false }
|
|
190
|
+
connected_queue&.push(e)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
@tls_verified
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def verify_tls_peer!
|
|
198
|
+
socket = @ws&.instance_variable_get(:@socket)
|
|
199
|
+
return unless socket.is_a?(OpenSSL::SSL::SSLSocket)
|
|
200
|
+
|
|
201
|
+
socket.post_connection_check(URI.parse(@api_url).host)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def send_connect_auth
|
|
205
|
+
auth = JSON.generate({ token: @token })
|
|
206
|
+
send_raw("#{EIO_MESSAGE}#{SIO_CONNECT}#{auth}")
|
|
207
|
+
end
|
|
208
|
+
|
|
144
209
|
def build_ws_url
|
|
145
210
|
parsed = URI.parse(@api_url)
|
|
146
211
|
ws_scheme = parsed.scheme == 'https' ? 'wss' : 'ws'
|
|
@@ -160,6 +225,11 @@ module Daytona
|
|
|
160
225
|
def handle_raw_message(raw, connected_queue)
|
|
161
226
|
return if raw.nil? || raw.empty?
|
|
162
227
|
|
|
228
|
+
# Drop every frame from a peer whose hostname is not yet verified. The
|
|
229
|
+
# reader thread can run before #connect verifies, so this is the gate that
|
|
230
|
+
# stops a wrong-host peer from injecting events or eliciting the auth frame.
|
|
231
|
+
return unless ensure_tls_verified!(connected_queue)
|
|
232
|
+
|
|
163
233
|
# Track all server activity for health monitoring.
|
|
164
234
|
# If the server stops sending ANY data (pings, events, etc.)
|
|
165
235
|
# the health monitor will detect the dead connection.
|
|
@@ -175,9 +245,10 @@ module Daytona
|
|
|
175
245
|
rescue JSON::ParserError
|
|
176
246
|
# Use default ping interval
|
|
177
247
|
end
|
|
178
|
-
#
|
|
179
|
-
|
|
180
|
-
|
|
248
|
+
# Signal the connecting thread instead of sending the CONNECT auth frame
|
|
249
|
+
# here: the token must not leave this process until the connecting thread
|
|
250
|
+
# has completed TLS peer verification (see #connect).
|
|
251
|
+
connected_queue&.push(:open)
|
|
181
252
|
|
|
182
253
|
when EIO_PING
|
|
183
254
|
# Server heartbeat — respond immediately with PONG
|
data/lib/daytona/daytona.rb
CHANGED
|
@@ -99,7 +99,7 @@ module Daytona
|
|
|
99
99
|
#
|
|
100
100
|
# @param params [Daytona::CreateSandboxFromSnapshotParams, Daytona::CreateSandboxFromImageParams, Nil] Sandbox creation parameters
|
|
101
101
|
# @return [Daytona::Sandbox] The created sandbox
|
|
102
|
-
# @raise [Daytona::Sdk::Error] If auto_stop_interval, auto_pause_interval, or
|
|
102
|
+
# @raise [Daytona::Sdk::Error] If auto_stop_interval, auto_pause_interval, auto_archive_interval, or ttl_minutes is negative,
|
|
103
103
|
# or if auto_stop_interval and auto_pause_interval are both non-zero
|
|
104
104
|
def create(params = nil, on_snapshot_create_logs: nil)
|
|
105
105
|
if params.nil?
|
|
@@ -237,7 +237,7 @@ module Daytona
|
|
|
237
237
|
# @param timeout [Numeric] Maximum wait time in seconds (defaults to 60 s).
|
|
238
238
|
# @param on_snapshot_create_logs [Proc]
|
|
239
239
|
# @return [Daytona::Sandbox] The created sandbox
|
|
240
|
-
# @raise [Daytona::Sdk::Error] If auto_stop_interval, auto_pause_interval, or
|
|
240
|
+
# @raise [Daytona::Sdk::Error] If auto_stop_interval, auto_pause_interval, auto_archive_interval, or ttl_minutes is negative,
|
|
241
241
|
# or if auto_stop_interval and auto_pause_interval are both non-zero
|
|
242
242
|
def _create(params, timeout: 60, on_snapshot_create_logs: nil)
|
|
243
243
|
raise Sdk::Error, 'Timeout must be a non-negative number' if timeout.negative?
|
|
@@ -261,6 +261,8 @@ module Daytona
|
|
|
261
261
|
raise Sdk::Error, 'auto_archive_interval must be a non-negative integer'
|
|
262
262
|
end
|
|
263
263
|
|
|
264
|
+
raise Sdk::Error, 'ttl_minutes must be a non-negative integer' if params.ttl_minutes&.negative?
|
|
265
|
+
|
|
264
266
|
labels = params.labels&.dup || {}
|
|
265
267
|
labels[CODE_TOOLBOX_LANGUAGE_LABEL] = params.language.to_s if params.language
|
|
266
268
|
|
|
@@ -274,6 +276,7 @@ module Daytona
|
|
|
274
276
|
auto_pause_interval: params.auto_pause_interval,
|
|
275
277
|
auto_archive_interval: params.auto_archive_interval,
|
|
276
278
|
auto_delete_interval: params.auto_delete_interval,
|
|
279
|
+
ttl_minutes: params.ttl_minutes,
|
|
277
280
|
volumes: params.volumes,
|
|
278
281
|
secrets: params.secrets&.map { |env_var, secret_name| { env_var.to_s => secret_name.to_s } },
|
|
279
282
|
network_block_all: params.network_block_all,
|
data/lib/daytona/sandbox.rb
CHANGED
|
@@ -140,6 +140,9 @@ module Daytona
|
|
|
140
140
|
# Not returned by list results; call #refresh on each item to populate.
|
|
141
141
|
attr_reader :build_info
|
|
142
142
|
|
|
143
|
+
# @return [String, nil] When the sandbox will be automatically destroyed (nil if no TTL is set)
|
|
144
|
+
attr_reader :auto_destroy_at
|
|
145
|
+
|
|
143
146
|
# @return [String] The creation timestamp of the sandbox
|
|
144
147
|
attr_reader :created_at
|
|
145
148
|
|
|
@@ -354,6 +357,19 @@ module Daytona
|
|
|
354
357
|
@auto_pause_interval = interval
|
|
355
358
|
end
|
|
356
359
|
|
|
360
|
+
# Sets the TTL (time to live) for the Sandbox.
|
|
361
|
+
# The TTL is re-anchored from the current time. When it elapses the Sandbox is destroyed,
|
|
362
|
+
# regardless of its current state. Use 0 to disable.
|
|
363
|
+
#
|
|
364
|
+
# @param minutes [Integer]
|
|
365
|
+
# @return [void]
|
|
366
|
+
# @raise [Daytona::Sdk::Error]
|
|
367
|
+
def ttl_minutes=(minutes)
|
|
368
|
+
raise Sdk::Error, 'TTL must be a non-negative integer' if minutes.negative?
|
|
369
|
+
|
|
370
|
+
sandbox_api.set_ttl(id, minutes)
|
|
371
|
+
end
|
|
372
|
+
|
|
357
373
|
# Creates an SSH access token for the sandbox.
|
|
358
374
|
#
|
|
359
375
|
# @param expires_in_minutes [Integer] TThe number of minutes the SSH access token will be valid for
|
|
@@ -821,6 +837,7 @@ module Daytona
|
|
|
821
837
|
:experimental_fork, :experimental_create_snapshot, :pause
|
|
822
838
|
|
|
823
839
|
instrument :archive, :auto_archive_interval=, :auto_delete_interval=, :auto_pause_interval=, :auto_stop_interval=,
|
|
840
|
+
:ttl_minutes=,
|
|
824
841
|
:update_network_settings, :update_secrets, :update_env,
|
|
825
842
|
:create_ssh_access, :delete, :get_user_home_dir, :get_work_dir, :get_metrics, :get_metrics_latest,
|
|
826
843
|
:labels=,
|
|
@@ -973,6 +990,7 @@ module Daytona
|
|
|
973
990
|
@auto_pause_interval = sandbox_dto.auto_pause_interval
|
|
974
991
|
@auto_archive_interval = sandbox_dto.auto_archive_interval
|
|
975
992
|
@auto_delete_interval = sandbox_dto.auto_delete_interval
|
|
993
|
+
@auto_destroy_at = sandbox_dto.auto_destroy_at
|
|
976
994
|
@created_at = sandbox_dto.created_at
|
|
977
995
|
@updated_at = sandbox_dto.updated_at
|
|
978
996
|
@last_activity_at = sandbox_dto.last_activity_at
|
data/lib/daytona/sdk/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.200.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daytona Platforms Inc.
|
|
@@ -85,42 +85,42 @@ dependencies:
|
|
|
85
85
|
requirements:
|
|
86
86
|
- - '='
|
|
87
87
|
- !ruby/object:Gem::Version
|
|
88
|
-
version: 0.
|
|
88
|
+
version: 0.200.0
|
|
89
89
|
type: :runtime
|
|
90
90
|
prerelease: false
|
|
91
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
92
92
|
requirements:
|
|
93
93
|
- - '='
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
|
-
version: 0.
|
|
95
|
+
version: 0.200.0
|
|
96
96
|
- !ruby/object:Gem::Dependency
|
|
97
97
|
name: daytona_api_client
|
|
98
98
|
requirement: !ruby/object:Gem::Requirement
|
|
99
99
|
requirements:
|
|
100
100
|
- - '='
|
|
101
101
|
- !ruby/object:Gem::Version
|
|
102
|
-
version: 0.
|
|
102
|
+
version: 0.200.0
|
|
103
103
|
type: :runtime
|
|
104
104
|
prerelease: false
|
|
105
105
|
version_requirements: !ruby/object:Gem::Requirement
|
|
106
106
|
requirements:
|
|
107
107
|
- - '='
|
|
108
108
|
- !ruby/object:Gem::Version
|
|
109
|
-
version: 0.
|
|
109
|
+
version: 0.200.0
|
|
110
110
|
- !ruby/object:Gem::Dependency
|
|
111
111
|
name: daytona_toolbox_api_client
|
|
112
112
|
requirement: !ruby/object:Gem::Requirement
|
|
113
113
|
requirements:
|
|
114
114
|
- - '='
|
|
115
115
|
- !ruby/object:Gem::Version
|
|
116
|
-
version: 0.
|
|
116
|
+
version: 0.200.0
|
|
117
117
|
type: :runtime
|
|
118
118
|
prerelease: false
|
|
119
119
|
version_requirements: !ruby/object:Gem::Requirement
|
|
120
120
|
requirements:
|
|
121
121
|
- - '='
|
|
122
122
|
- !ruby/object:Gem::Version
|
|
123
|
-
version: 0.
|
|
123
|
+
version: 0.200.0
|
|
124
124
|
- !ruby/object:Gem::Dependency
|
|
125
125
|
name: dotenv
|
|
126
126
|
requirement: !ruby/object:Gem::Requirement
|