daytona 0.200.1 → 0.201.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/code_interpreter.rb +6 -6
- data/lib/daytona/common/response.rb +2 -0
- data/lib/daytona/computer_use.rb +62 -62
- data/lib/daytona/daytona.rb +9 -0
- data/lib/daytona/file_system.rb +36 -28
- data/lib/daytona/git.rb +21 -31
- data/lib/daytona/lsp_server.rb +20 -2
- data/lib/daytona/object_storage.rb +2 -5
- data/lib/daytona/process.rb +44 -4
- data/lib/daytona/sandbox.rb +93 -9
- data/lib/daytona/sdk/deprecated.rb +18 -0
- data/lib/daytona/sdk/errors.rb +279 -0
- data/lib/daytona/sdk/file_download_patch.rb +131 -0
- data/lib/daytona/sdk/version.rb +1 -1
- data/lib/daytona/sdk.rb +7 -9
- data/lib/daytona/snapshot_service.rb +2 -1
- data/lib/daytona/utils/file_url_signing.rb +56 -0
- metadata +19 -9
data/lib/daytona/git.rb
CHANGED
|
@@ -47,8 +47,8 @@ module Daytona
|
|
|
47
47
|
toolbox_api.add_files(DaytonaToolboxApiClient::GitAddRequest.new(path:, files:))
|
|
48
48
|
rescue DaytonaToolboxApiClient::ApiError => e
|
|
49
49
|
raise map_api_error(e, 'Failed to add files')
|
|
50
|
-
rescue
|
|
51
|
-
raise Sdk
|
|
50
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
51
|
+
raise Sdk.wrap_error(e, 'Failed to add files')
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
# Lists branches in the repository.
|
|
@@ -65,8 +65,8 @@ module Daytona
|
|
|
65
65
|
toolbox_api.list_branches(path)
|
|
66
66
|
rescue DaytonaToolboxApiClient::ApiError => e
|
|
67
67
|
raise map_api_error(e, 'Failed to list branches')
|
|
68
|
-
rescue
|
|
69
|
-
raise Sdk
|
|
68
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
69
|
+
raise Sdk.wrap_error(e, 'Failed to list branches')
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
# Clones a Git repository into the specified path. It supports
|
|
@@ -126,8 +126,8 @@ module Daytona
|
|
|
126
126
|
)
|
|
127
127
|
rescue DaytonaToolboxApiClient::ApiError => e
|
|
128
128
|
raise map_api_error(e, 'Failed to clone repository')
|
|
129
|
-
rescue
|
|
130
|
-
raise Sdk
|
|
129
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
130
|
+
raise Sdk.wrap_error(e, 'Failed to clone repository')
|
|
131
131
|
end
|
|
132
132
|
|
|
133
133
|
# Creates a new commit with the staged changes. Make sure to stage
|
|
@@ -160,8 +160,8 @@ module Daytona
|
|
|
160
160
|
GitCommitResponse.new(sha: response._hash)
|
|
161
161
|
rescue DaytonaToolboxApiClient::ApiError => e
|
|
162
162
|
raise map_api_error(e, 'Failed to commit changes')
|
|
163
|
-
rescue
|
|
164
|
-
raise Sdk
|
|
163
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
164
|
+
raise Sdk.wrap_error(e, 'Failed to commit changes')
|
|
165
165
|
end
|
|
166
166
|
|
|
167
167
|
# Pushes all local commits on the current branch to the remote
|
|
@@ -197,8 +197,8 @@ module Daytona
|
|
|
197
197
|
)
|
|
198
198
|
rescue DaytonaToolboxApiClient::ApiError => e
|
|
199
199
|
raise map_api_error(e, 'Failed to push changes')
|
|
200
|
-
rescue
|
|
201
|
-
raise Sdk
|
|
200
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
201
|
+
raise Sdk.wrap_error(e, 'Failed to push changes')
|
|
202
202
|
end
|
|
203
203
|
|
|
204
204
|
# Pulls changes from the remote repository. If the remote repository requires authentication,
|
|
@@ -232,8 +232,8 @@ module Daytona
|
|
|
232
232
|
)
|
|
233
233
|
rescue DaytonaToolboxApiClient::ApiError => e
|
|
234
234
|
raise map_api_error(e, 'Failed to pull changes')
|
|
235
|
-
rescue
|
|
236
|
-
raise Sdk
|
|
235
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
236
|
+
raise Sdk.wrap_error(e, 'Failed to pull changes')
|
|
237
237
|
end
|
|
238
238
|
|
|
239
239
|
# Gets the current Git repository status.
|
|
@@ -252,8 +252,8 @@ module Daytona
|
|
|
252
252
|
toolbox_api.get_status(path)
|
|
253
253
|
rescue DaytonaToolboxApiClient::ApiError => e
|
|
254
254
|
raise map_api_error(e, 'Failed to get status')
|
|
255
|
-
rescue
|
|
256
|
-
raise Sdk
|
|
255
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
256
|
+
raise Sdk.wrap_error(e, 'Failed to get status')
|
|
257
257
|
end
|
|
258
258
|
|
|
259
259
|
# Checkout branch in the repository.
|
|
@@ -273,8 +273,8 @@ module Daytona
|
|
|
273
273
|
)
|
|
274
274
|
rescue DaytonaToolboxApiClient::ApiError => e
|
|
275
275
|
raise map_api_error(e, 'Failed to checkout branch')
|
|
276
|
-
rescue
|
|
277
|
-
raise Sdk
|
|
276
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
277
|
+
raise Sdk.wrap_error(e, 'Failed to checkout branch')
|
|
278
278
|
end
|
|
279
279
|
|
|
280
280
|
# Create branch in the repository.
|
|
@@ -295,8 +295,8 @@ module Daytona
|
|
|
295
295
|
)
|
|
296
296
|
rescue DaytonaToolboxApiClient::ApiError => e
|
|
297
297
|
raise map_api_error(e, 'Failed to create branch')
|
|
298
|
-
rescue
|
|
299
|
-
raise Sdk
|
|
298
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
299
|
+
raise Sdk.wrap_error(e, 'Failed to create branch')
|
|
300
300
|
end
|
|
301
301
|
|
|
302
302
|
# Delete branch in the repository.
|
|
@@ -316,8 +316,8 @@ module Daytona
|
|
|
316
316
|
)
|
|
317
317
|
rescue DaytonaToolboxApiClient::ApiError => e
|
|
318
318
|
raise map_api_error(e, 'Failed to delete branch')
|
|
319
|
-
rescue
|
|
320
|
-
raise Sdk
|
|
319
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
320
|
+
raise Sdk.wrap_error(e, 'Failed to delete branch')
|
|
321
321
|
end
|
|
322
322
|
|
|
323
323
|
# Initializes a new Git repository at the specified path.
|
|
@@ -544,17 +544,7 @@ module Daytona
|
|
|
544
544
|
attr_reader :otel_state
|
|
545
545
|
|
|
546
546
|
def map_api_error(api_error, prefix)
|
|
547
|
-
|
|
548
|
-
case api_error.code
|
|
549
|
-
when 400 then Sdk::ValidationError.new(msg)
|
|
550
|
-
when 401 then Sdk::AuthenticationError.new(msg)
|
|
551
|
-
when 403 then Sdk::ForbiddenError.new(msg)
|
|
552
|
-
when 404 then Sdk::NotFoundError.new(msg)
|
|
553
|
-
when 409 then Sdk::ConflictError.new(msg)
|
|
554
|
-
when 429 then Sdk::RateLimitError.new(msg)
|
|
555
|
-
when 500..599 then Sdk::ServerError.new(msg)
|
|
556
|
-
else Sdk::Error.new(msg)
|
|
557
|
-
end
|
|
547
|
+
Sdk.wrap_error(api_error, prefix)
|
|
558
548
|
end
|
|
559
549
|
end
|
|
560
550
|
end
|
data/lib/daytona/lsp_server.rb
CHANGED
|
@@ -59,6 +59,8 @@ module Daytona
|
|
|
59
59
|
position: DaytonaToolboxApiClient::LspPosition.new(line: position.line, character: position.character)
|
|
60
60
|
)
|
|
61
61
|
)
|
|
62
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
63
|
+
raise Sdk.wrap_error(e, 'Failed to get LSP completions')
|
|
62
64
|
end
|
|
63
65
|
|
|
64
66
|
# Notify the language server that a file has been closed.
|
|
@@ -71,6 +73,8 @@ module Daytona
|
|
|
71
73
|
toolbox_api.did_close(
|
|
72
74
|
DaytonaToolboxApiClient::LspDocumentRequest.new(language_id:, path_to_project:, uri: uri(path))
|
|
73
75
|
)
|
|
76
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
77
|
+
raise Sdk.wrap_error(e, 'Failed to notify LSP server of closed document')
|
|
74
78
|
end
|
|
75
79
|
|
|
76
80
|
# Notifies the language server that a file has been opened.
|
|
@@ -84,20 +88,30 @@ module Daytona
|
|
|
84
88
|
toolbox_api.did_open(
|
|
85
89
|
DaytonaToolboxApiClient::LspDocumentRequest.new(language_id:, path_to_project:, uri: uri(path))
|
|
86
90
|
)
|
|
91
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
92
|
+
raise Sdk.wrap_error(e, 'Failed to notify LSP server of opened document')
|
|
87
93
|
end
|
|
88
94
|
|
|
89
95
|
# Gets symbol information (functions, classes, variables, etc.) from a document.
|
|
90
96
|
#
|
|
91
97
|
# @param path [String]
|
|
92
98
|
# @return [Array<DaytonaToolboxApiClient::LspSymbol]
|
|
93
|
-
def document_symbols(path)
|
|
99
|
+
def document_symbols(path)
|
|
100
|
+
toolbox_api.document_symbols(language_id, path_to_project, uri(path))
|
|
101
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
102
|
+
raise Sdk.wrap_error(e, 'Failed to get LSP document symbols')
|
|
103
|
+
end
|
|
94
104
|
|
|
95
105
|
# Searches for symbols matching the query string across all files
|
|
96
106
|
# in the Sandbox.
|
|
97
107
|
#
|
|
98
108
|
# @param query [String]
|
|
99
109
|
# @return [Array<DaytonaToolboxApiClient::LspSymbol]
|
|
100
|
-
def sandbox_symbols(query)
|
|
110
|
+
def sandbox_symbols(query)
|
|
111
|
+
toolbox_api.workspace_symbols(query, language_id, path_to_project)
|
|
112
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
113
|
+
raise Sdk.wrap_error(e, 'Failed to get LSP workspace symbols')
|
|
114
|
+
end
|
|
101
115
|
|
|
102
116
|
# Starts the language server.
|
|
103
117
|
# This method must be called before using any other LSP functionality.
|
|
@@ -108,6 +122,8 @@ module Daytona
|
|
|
108
122
|
toolbox_api.start(
|
|
109
123
|
DaytonaToolboxApiClient::LspServerRequest.new(language_id:, path_to_project:)
|
|
110
124
|
)
|
|
125
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
126
|
+
raise Sdk.wrap_error(e, 'Failed to start LSP server')
|
|
111
127
|
end
|
|
112
128
|
|
|
113
129
|
# Stops the language server.
|
|
@@ -119,6 +135,8 @@ module Daytona
|
|
|
119
135
|
toolbox_api.stop(
|
|
120
136
|
DaytonaToolboxApiClient::LspServerRequest.new(language_id:, path_to_project:)
|
|
121
137
|
)
|
|
138
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
139
|
+
raise Sdk.wrap_error(e, 'Failed to stop LSP server')
|
|
122
140
|
end
|
|
123
141
|
|
|
124
142
|
instrument :completions, :did_close, :did_open, :document_symbols, :sandbox_symbols,
|
|
@@ -25,9 +25,9 @@ module Daytona
|
|
|
25
25
|
# @param aws_secret_access_key [String] The secret access key for the object storage service
|
|
26
26
|
# @param aws_session_token [String] The session token for the object storage service
|
|
27
27
|
# @param bucket_name [String] The name of the bucket to use (defaults to "daytona-volume-builds")
|
|
28
|
-
# @param region [String]
|
|
28
|
+
# @param region [String] Region of the storage backend
|
|
29
29
|
def initialize(endpoint_url:, aws_access_key_id:, aws_secret_access_key:, aws_session_token:, # rubocop:disable Metrics/ParameterLists
|
|
30
|
-
bucket_name: DEFAULT_BUCKET_NAME
|
|
30
|
+
region:, bucket_name: DEFAULT_BUCKET_NAME)
|
|
31
31
|
@bucket_name = bucket_name
|
|
32
32
|
@s3_client = Aws::S3::Client.new(
|
|
33
33
|
region:,
|
|
@@ -165,8 +165,5 @@ module Daytona
|
|
|
165
165
|
|
|
166
166
|
DEFAULT_BUCKET_NAME = 'daytona-volume-builds'
|
|
167
167
|
private_constant :DEFAULT_BUCKET_NAME
|
|
168
|
-
|
|
169
|
-
DEFAULT_REGION = 'us-east-1'
|
|
170
|
-
private_constant :DEFAULT_REGION
|
|
171
168
|
end
|
|
172
169
|
end
|
data/lib/daytona/process.rb
CHANGED
|
@@ -68,6 +68,8 @@ module Daytona
|
|
|
68
68
|
result:,
|
|
69
69
|
artifacts: ExecutionArtifacts.new(result, [])
|
|
70
70
|
)
|
|
71
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
72
|
+
raise Sdk.wrap_error(e, 'Failed to execute command')
|
|
71
73
|
end
|
|
72
74
|
|
|
73
75
|
# Execute code in the Sandbox using the appropriate language runtime
|
|
@@ -99,6 +101,8 @@ module Daytona
|
|
|
99
101
|
Charts.parse_chart(c)
|
|
100
102
|
end)
|
|
101
103
|
)
|
|
104
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
105
|
+
raise Sdk.wrap_error(e, 'Failed to run code')
|
|
102
106
|
end
|
|
103
107
|
|
|
104
108
|
# Creates a new long-running background session in the Sandbox
|
|
@@ -118,6 +122,8 @@ module Daytona
|
|
|
118
122
|
# sandbox.process.delete_session(session_id)
|
|
119
123
|
def create_session(session_id)
|
|
120
124
|
toolbox_api.create_session(DaytonaToolboxApiClient::CreateSessionRequest.new(session_id:))
|
|
125
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
126
|
+
raise Sdk.wrap_error(e, 'Failed to create session')
|
|
121
127
|
end
|
|
122
128
|
|
|
123
129
|
# Gets a session in the Sandbox
|
|
@@ -130,7 +136,11 @@ module Daytona
|
|
|
130
136
|
# session.commands.each do |cmd|
|
|
131
137
|
# puts "Command: #{cmd.command}"
|
|
132
138
|
# end
|
|
133
|
-
def get_session(session_id)
|
|
139
|
+
def get_session(session_id)
|
|
140
|
+
toolbox_api.get_session(session_id)
|
|
141
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
142
|
+
raise Sdk.wrap_error(e, 'Failed to get session')
|
|
143
|
+
end
|
|
134
144
|
|
|
135
145
|
# Gets the Sandbox entrypoint session
|
|
136
146
|
#
|
|
@@ -141,7 +151,11 @@ module Daytona
|
|
|
141
151
|
# session.commands.each do |cmd|
|
|
142
152
|
# puts "Command: #{cmd.command}"
|
|
143
153
|
# end
|
|
144
|
-
def get_entrypoint_session
|
|
154
|
+
def get_entrypoint_session
|
|
155
|
+
toolbox_api.get_entrypoint_session
|
|
156
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
157
|
+
raise Sdk.wrap_error(e, 'Failed to get entrypoint session')
|
|
158
|
+
end
|
|
145
159
|
|
|
146
160
|
# Gets information about a specific command executed in a session
|
|
147
161
|
#
|
|
@@ -156,6 +170,8 @@ module Daytona
|
|
|
156
170
|
# end
|
|
157
171
|
def get_session_command(session_id:, command_id:)
|
|
158
172
|
toolbox_api.get_session_command(session_id, command_id)
|
|
173
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
174
|
+
raise Sdk.wrap_error(e, 'Failed to get session command')
|
|
159
175
|
end
|
|
160
176
|
|
|
161
177
|
# Executes a command in the session
|
|
@@ -196,6 +212,8 @@ module Daytona
|
|
|
196
212
|
exit_code: response.exit_code,
|
|
197
213
|
additional_properties: {}
|
|
198
214
|
)
|
|
215
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
216
|
+
raise Sdk.wrap_error(e, 'Failed to execute session command')
|
|
199
217
|
end
|
|
200
218
|
|
|
201
219
|
# Get the logs for a command executed in a session
|
|
@@ -211,6 +229,8 @@ module Daytona
|
|
|
211
229
|
def get_session_command_logs(session_id:, command_id:)
|
|
212
230
|
response = toolbox_api.get_session_command_logs(session_id, command_id)
|
|
213
231
|
SessionCommandLogsResponse.new(output: response.output, stdout: response.stdout, stderr: response.stderr)
|
|
232
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
233
|
+
raise Sdk.wrap_error(e, 'Failed to get session command logs')
|
|
214
234
|
end
|
|
215
235
|
|
|
216
236
|
# Asynchronously retrieves and processes the logs for a command executed in a session as they become available
|
|
@@ -282,6 +302,8 @@ module Daytona
|
|
|
282
302
|
def get_entrypoint_logs
|
|
283
303
|
response = toolbox_api.get_entrypoint_logs
|
|
284
304
|
SessionCommandLogsResponse.new(output: response.output, stdout: response.stdout, stderr: response.stderr)
|
|
305
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
306
|
+
raise Sdk.wrap_error(e, 'Failed to get entrypoint logs')
|
|
285
307
|
end
|
|
286
308
|
|
|
287
309
|
# Asynchronously retrieves and processes the sandbox entrypoint logs as they become available
|
|
@@ -353,6 +375,8 @@ module Daytona
|
|
|
353
375
|
command_id,
|
|
354
376
|
DaytonaToolboxApiClient::SessionSendInputRequest.new(data:)
|
|
355
377
|
)
|
|
378
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
379
|
+
raise Sdk.wrap_error(e, 'Failed to send session command input')
|
|
356
380
|
end
|
|
357
381
|
|
|
358
382
|
#
|
|
@@ -364,7 +388,11 @@ module Daytona
|
|
|
364
388
|
# puts "Session #{session.session_id}:"
|
|
365
389
|
# puts " Commands: #{session.commands.length}"
|
|
366
390
|
# end
|
|
367
|
-
def list_sessions
|
|
391
|
+
def list_sessions
|
|
392
|
+
toolbox_api.list_sessions
|
|
393
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
394
|
+
raise Sdk.wrap_error(e, 'Failed to list sessions')
|
|
395
|
+
end
|
|
368
396
|
|
|
369
397
|
# Terminates and removes a session from the Sandbox, cleaning up any resources associated with it
|
|
370
398
|
#
|
|
@@ -377,7 +405,11 @@ module Daytona
|
|
|
377
405
|
#
|
|
378
406
|
# # Clean up when done
|
|
379
407
|
# sandbox.process.delete_session("temp-session")
|
|
380
|
-
def delete_session(session_id)
|
|
408
|
+
def delete_session(session_id)
|
|
409
|
+
toolbox_api.delete_session(session_id)
|
|
410
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
411
|
+
raise Sdk.wrap_error(e, 'Failed to delete session')
|
|
412
|
+
end
|
|
381
413
|
|
|
382
414
|
# Creates a new PTY (pseudo-terminal) session in the Sandbox.
|
|
383
415
|
#
|
|
@@ -499,6 +531,8 @@ module Daytona
|
|
|
499
531
|
rows: pty_size.rows
|
|
500
532
|
)
|
|
501
533
|
)
|
|
534
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
535
|
+
raise Sdk.wrap_error(e, 'Failed to resize PTY session')
|
|
502
536
|
end
|
|
503
537
|
|
|
504
538
|
# Deletes a PTY session, terminating the associated process
|
|
@@ -510,6 +544,8 @@ module Daytona
|
|
|
510
544
|
# sandbox.process.delete_pty_session("my-pty")
|
|
511
545
|
def delete_pty_session(session_id)
|
|
512
546
|
toolbox_api.delete_pty_session(session_id)
|
|
547
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
548
|
+
raise Sdk.wrap_error(e, 'Failed to delete PTY session')
|
|
513
549
|
end
|
|
514
550
|
|
|
515
551
|
# Lists all PTY sessions in the Sandbox
|
|
@@ -526,6 +562,8 @@ module Daytona
|
|
|
526
562
|
return [] if response.nil?
|
|
527
563
|
|
|
528
564
|
response.respond_to?(:sessions) ? (response.sessions || []) : response
|
|
565
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
566
|
+
raise Sdk.wrap_error(e, 'Failed to list PTY sessions')
|
|
529
567
|
end
|
|
530
568
|
|
|
531
569
|
# Gets detailed information about a specific PTY session
|
|
@@ -546,6 +584,8 @@ module Daytona
|
|
|
546
584
|
# puts "Terminal Size: #{session_info.cols}x#{session_info.rows}"
|
|
547
585
|
def get_pty_session_info(session_id)
|
|
548
586
|
toolbox_api.get_pty_session(session_id)
|
|
587
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
588
|
+
raise Sdk.wrap_error(e, 'Failed to get PTY session info')
|
|
549
589
|
end
|
|
550
590
|
|
|
551
591
|
instrument :exec, :code_run, :create_session, :get_session, :get_session_command,
|
data/lib/daytona/sandbox.rb
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
require 'time'
|
|
7
7
|
require 'timeout'
|
|
8
|
+
require_relative 'utils/file_url_signing'
|
|
8
9
|
|
|
9
10
|
module Daytona
|
|
10
11
|
# A single point-in-time sample of historical Sandbox resource usage.
|
|
@@ -196,6 +197,8 @@ module Daytona
|
|
|
196
197
|
@analytics_api_url_provider = analytics_api_url_provider
|
|
197
198
|
@subscription_manager = subscription_manager
|
|
198
199
|
@sub_id = nil
|
|
200
|
+
@signing_key = nil
|
|
201
|
+
@signing_key_fetched_at = 0
|
|
199
202
|
|
|
200
203
|
# Create toolbox API clients with dynamic configuration
|
|
201
204
|
@toolbox_api_config = build_toolbox_api_config
|
|
@@ -435,8 +438,8 @@ module Daytona
|
|
|
435
438
|
# puts "Sandbox user home: #{user_home_dir}"
|
|
436
439
|
def get_user_home_dir
|
|
437
440
|
@info_api.get_user_home_dir.dir
|
|
438
|
-
rescue
|
|
439
|
-
raise Sdk
|
|
441
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
442
|
+
raise Sdk.wrap_error(e, 'Failed to get user home directory')
|
|
440
443
|
end
|
|
441
444
|
|
|
442
445
|
# Gets the working directory path inside the Sandbox.
|
|
@@ -449,8 +452,8 @@ module Daytona
|
|
|
449
452
|
# puts "Sandbox working directory: #{work_dir}"
|
|
450
453
|
def get_work_dir
|
|
451
454
|
@info_api.get_work_dir.dir
|
|
452
|
-
rescue
|
|
453
|
-
raise Sdk
|
|
455
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
456
|
+
raise Sdk.wrap_error(e, 'Failed to get working directory path')
|
|
454
457
|
end
|
|
455
458
|
|
|
456
459
|
# Updates the Sandbox daemon's process environment. Newly spawned processes, sessions
|
|
@@ -524,6 +527,76 @@ module Daytona
|
|
|
524
527
|
sandbox_api.get_port_preview_url(id, port)
|
|
525
528
|
end
|
|
526
529
|
|
|
530
|
+
# Creates a pre-signed URL for downloading a file from the Sandbox.
|
|
531
|
+
#
|
|
532
|
+
# The URL works with any HTTP client without auth headers and stays valid across
|
|
533
|
+
# sandbox restarts (downloads succeed only while the sandbox is running). The signing
|
|
534
|
+
# key is cached locally for up to 15 seconds; if the key was rotated from another
|
|
535
|
+
# client, URLs may be rejected until the cache refreshes.
|
|
536
|
+
#
|
|
537
|
+
# @param path [String] Path to the file in the Sandbox.
|
|
538
|
+
# @param ttl_seconds [Integer, nil] How long the URL stays valid, in seconds.
|
|
539
|
+
# Defaults to 3600. Zero or negative means the URL never expires.
|
|
540
|
+
# @return [String] Pre-signed download URL.
|
|
541
|
+
# @raise [Daytona::Sdk::Error] if the signing key cannot be fetched.
|
|
542
|
+
#
|
|
543
|
+
# @example
|
|
544
|
+
# url = sandbox.download_url('/home/user/report.pdf')
|
|
545
|
+
# # curl "$url" -o report.pdf
|
|
546
|
+
def download_url(path, ttl_seconds: nil)
|
|
547
|
+
Utils::FileUrlSigning.build_signed_file_url(
|
|
548
|
+
@toolbox_proxy_url,
|
|
549
|
+
id,
|
|
550
|
+
'/files/download',
|
|
551
|
+
'GET',
|
|
552
|
+
path,
|
|
553
|
+
ensure_signing_key,
|
|
554
|
+
ttl_seconds
|
|
555
|
+
)
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
# Creates a pre-signed URL for uploading a file to the Sandbox.
|
|
559
|
+
#
|
|
560
|
+
# Send a POST request with the file as multipart/form-data. The URL works with any
|
|
561
|
+
# HTTP client without auth headers. The signing key is cached locally for up to
|
|
562
|
+
# 15 seconds; if the key was rotated from another client, URLs may be rejected
|
|
563
|
+
# until the cache refreshes.
|
|
564
|
+
#
|
|
565
|
+
# @param path [String] Destination path for the uploaded file in the Sandbox.
|
|
566
|
+
# @param ttl_seconds [Integer, nil] How long the URL stays valid, in seconds.
|
|
567
|
+
# Defaults to 3600. Zero or negative means the URL never expires.
|
|
568
|
+
# @return [String] Pre-signed upload URL.
|
|
569
|
+
# @raise [Daytona::Sdk::Error] if the signing key cannot be fetched.
|
|
570
|
+
#
|
|
571
|
+
# @example
|
|
572
|
+
# url = sandbox.upload_url('/home/user/data.bin')
|
|
573
|
+
# # curl -X POST -F "file=@local.bin" "$url"
|
|
574
|
+
def upload_url(path, ttl_seconds: nil)
|
|
575
|
+
Utils::FileUrlSigning.build_signed_file_url(
|
|
576
|
+
@toolbox_proxy_url,
|
|
577
|
+
id,
|
|
578
|
+
'/files/upload-v2',
|
|
579
|
+
'POST',
|
|
580
|
+
path,
|
|
581
|
+
ensure_signing_key,
|
|
582
|
+
ttl_seconds
|
|
583
|
+
)
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
# Rotates the sandbox signing key, invalidating all previously signed URLs.
|
|
587
|
+
#
|
|
588
|
+
# @return [void]
|
|
589
|
+
# @raise [DaytonaApiClient::ApiError] if the signing key rotation request fails.
|
|
590
|
+
#
|
|
591
|
+
# @example
|
|
592
|
+
# sandbox.rotate_signing_key
|
|
593
|
+
# puts 'All previously signed URLs are now invalid.'
|
|
594
|
+
def rotate_signing_key
|
|
595
|
+
@signing_key = @sandbox_api.rotate_signing_key(id)
|
|
596
|
+
@signing_key_fetched_at = Time.now.to_f
|
|
597
|
+
nil
|
|
598
|
+
end
|
|
599
|
+
|
|
527
600
|
# Creates a signed preview URL for the sandbox at the specified port.
|
|
528
601
|
#
|
|
529
602
|
# @param port [Integer] The port to open the preview link on
|
|
@@ -570,8 +643,8 @@ module Daytona
|
|
|
570
643
|
def refresh_activity
|
|
571
644
|
sandbox_api.update_last_activity(id)
|
|
572
645
|
nil
|
|
573
|
-
rescue
|
|
574
|
-
raise Sdk
|
|
646
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
647
|
+
raise Sdk.wrap_error(e, 'Failed to refresh sandbox activity')
|
|
575
648
|
end
|
|
576
649
|
|
|
577
650
|
# Revokes an SSH access token for the sandbox.
|
|
@@ -619,8 +692,8 @@ module Daytona
|
|
|
619
692
|
error_states: [DaytonaApiClient::SandboxState::ERROR, DaytonaApiClient::SandboxState::BUILD_FAILED]
|
|
620
693
|
)
|
|
621
694
|
end
|
|
622
|
-
rescue
|
|
623
|
-
raise Sdk
|
|
695
|
+
rescue *Sdk::API_ERROR_CLASSES => e
|
|
696
|
+
raise Sdk.wrap_error(e, 'Failed to recover sandbox')
|
|
624
697
|
end
|
|
625
698
|
|
|
626
699
|
# Stops the Sandbox and waits for it to be stopped.
|
|
@@ -841,7 +914,8 @@ module Daytona
|
|
|
841
914
|
:update_network_settings, :update_secrets, :update_env,
|
|
842
915
|
:create_ssh_access, :delete, :get_user_home_dir, :get_work_dir, :get_metrics, :get_metrics_latest,
|
|
843
916
|
:labels=,
|
|
844
|
-
:preview_url, :
|
|
917
|
+
:preview_url, :download_url, :upload_url, :rotate_signing_key,
|
|
918
|
+
:create_signed_preview_url, :expire_signed_preview_url,
|
|
845
919
|
:refresh, :refresh_activity, :revoke_ssh_access, :start, :recover, :stop,
|
|
846
920
|
:create_lsp_server, :validate_ssh_access, :wait_for_sandbox_start,
|
|
847
921
|
:wait_for_sandbox_stop, :resize, :wait_for_resize_complete,
|
|
@@ -964,6 +1038,16 @@ module Daytona
|
|
|
964
1038
|
@toolbox_api_config.base_path = uri.path.empty? ? '/' : uri.path
|
|
965
1039
|
end
|
|
966
1040
|
|
|
1041
|
+
def ensure_signing_key
|
|
1042
|
+
key = @signing_key
|
|
1043
|
+
if key.nil? || (Time.now.to_f - @signing_key_fetched_at) > 15
|
|
1044
|
+
key = @sandbox_api.get_sandbox_signing_key(id)
|
|
1045
|
+
@signing_key = key
|
|
1046
|
+
@signing_key_fetched_at = Time.now.to_f
|
|
1047
|
+
end
|
|
1048
|
+
key
|
|
1049
|
+
end
|
|
1050
|
+
|
|
967
1051
|
# @params sandbox_dto [DaytonaApiClient::Sandbox, DaytonaApiClient::SandboxListItem]
|
|
968
1052
|
# @return [void]
|
|
969
1053
|
def process_response(sandbox_dto) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Copyright Daytona Platforms Inc.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
# frozen_string_literal: true
|
|
5
|
+
|
|
6
|
+
# =============================================================================
|
|
7
|
+
# Backward-compatibility aliases. New functionality belongs in errors.rb, not
|
|
8
|
+
# here. Every name below exists solely so pre-typed-error-model user code
|
|
9
|
+
# keeps working, and this file can be removed as a whole in a future major
|
|
10
|
+
# release.
|
|
11
|
+
# =============================================================================
|
|
12
|
+
module Daytona
|
|
13
|
+
module Sdk
|
|
14
|
+
# @deprecated Use {BadRequestError} instead. Kept as an alias so existing
|
|
15
|
+
# `rescue Daytona::Sdk::ValidationError` blocks keep working.
|
|
16
|
+
ValidationError = BadRequestError
|
|
17
|
+
end
|
|
18
|
+
end
|