daytona 0.173.0 → 0.175.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/.rubocop.yml +6 -0
- data/lib/daytona/file_system.rb +42 -4
- data/lib/daytona/file_transfer.rb +183 -19
- data/lib/daytona/git.rb +34 -0
- data/lib/daytona/sdk/version.rb +1 -1
- data/lib/daytona/sdk.rb +7 -0
- metadata +5 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 17bac4d118263d5f248ac719aafb05357330d337618db08f599b59c5bcfbf584
|
|
4
|
+
data.tar.gz: ac0e288d40bc79b1df009fbb4585face3b667b8b01949e13f556a368fbb3bc1b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 575d0f018a49ebddb4e7dcc2e70e96b762f93ea6e4ad219413506809413f4629935102f870e8b67adb0ed1cb2418d5a4a7e78da946e684b18db1d011611c5c18
|
|
7
|
+
data.tar.gz: b3cdd82c715982e6221263e7b2141cfd1562261d5faf49d45d42738ee0fa2a7cbeaf96a7c0e6c577e1208e41f82547242d0c44f12316fdfff76ceb25377a2cad
|
data/.rubocop.yml
CHANGED
|
@@ -14,3 +14,9 @@ Style/Documentation:
|
|
|
14
14
|
|
|
15
15
|
Style/AccessorGrouping:
|
|
16
16
|
EnforcedStyle: separated
|
|
17
|
+
|
|
18
|
+
# RSpec describe / context blocks naturally span the whole file; the
|
|
19
|
+
# stock 25-line cap forces noise instead of catching real complexity.
|
|
20
|
+
Metrics/BlockLength:
|
|
21
|
+
Exclude:
|
|
22
|
+
- 'spec/**/*'
|
data/lib/daytona/file_system.rb
CHANGED
|
@@ -162,10 +162,17 @@ module Daytona
|
|
|
162
162
|
# based on the sandbox working directory.
|
|
163
163
|
# @param timeout [Integer] Timeout for the download operation in seconds. 0 means no timeout.
|
|
164
164
|
# Default is 30 minutes.
|
|
165
|
+
# @param on_progress [Proc, nil] Optional callback invoked with a Daytona::DownloadProgress
|
|
166
|
+
# struct containing bytes_received (Integer) and total_bytes (Integer or nil).
|
|
167
|
+
# @param cancel_event [#set?, nil] Optional cancellation token (anything responding to +set?+;
|
|
168
|
+
# the standard library's +Concurrent::Event+ or a small ad-hoc object both work). When set
|
|
169
|
+
# during streaming, the next chunk raises Daytona::Sdk::Error and the underlying HTTP
|
|
170
|
+
# connection is torn down.
|
|
165
171
|
# @yield [chunk] Yields each chunk of file content as it arrives
|
|
166
172
|
# @yieldparam chunk [String] A binary string chunk of file content
|
|
167
173
|
# @return [Enumerator, nil] An Enumerator yielding chunks if no block given, nil otherwise
|
|
168
|
-
# @raise [Daytona::Sdk::Error] If the file does not exist
|
|
174
|
+
# @raise [Daytona::Sdk::Error] If the file does not exist, the operation fails, or
|
|
175
|
+
# +cancel_event+ is set during streaming
|
|
169
176
|
#
|
|
170
177
|
# @example Stream to a local file without loading into memory
|
|
171
178
|
# File.open("local_copy.bin", "wb") do |f|
|
|
@@ -175,11 +182,12 @@ module Daytona
|
|
|
175
182
|
# @example Collect chunks with an Enumerator
|
|
176
183
|
# content = sandbox.fs.download_file_stream("workspace/data.json").reduce(:+)
|
|
177
184
|
# puts content
|
|
178
|
-
def download_file_stream(remote_path, timeout: 30 * 60, &)
|
|
179
|
-
return enum_for(__method__, remote_path, timeout:) unless block_given?
|
|
185
|
+
def download_file_stream(remote_path, timeout: 30 * 60, on_progress: nil, cancel_event: nil, &)
|
|
186
|
+
return enum_for(__method__, remote_path, timeout:, on_progress:, cancel_event:) unless block_given?
|
|
180
187
|
|
|
181
188
|
FileTransfer.stream_download(api_client: toolbox_api.api_client, remote_path: remote_path,
|
|
182
|
-
timeout: timeout,
|
|
189
|
+
timeout: timeout, on_progress: on_progress,
|
|
190
|
+
cancel_event: cancel_event, &)
|
|
183
191
|
nil
|
|
184
192
|
rescue StandardError => e
|
|
185
193
|
raise Sdk::Error, "Failed to download file: #{e.message}"
|
|
@@ -225,6 +233,36 @@ module Daytona
|
|
|
225
233
|
raise Sdk::Error, "Failed to upload file: #{e.message}"
|
|
226
234
|
end
|
|
227
235
|
|
|
236
|
+
# Streams +source+ to the Sandbox without buffering its contents in memory, with
|
|
237
|
+
# optional progress reporting.
|
|
238
|
+
#
|
|
239
|
+
# @param source [String, IO] A local file path or any IO-like object responding to
|
|
240
|
+
# +read(n)+. Strings that don't reference an existing file are uploaded as their
|
|
241
|
+
# raw bytes (still streamed, just from memory).
|
|
242
|
+
# @param remote_path [String] Destination path in the Sandbox.
|
|
243
|
+
# @param timeout [Integer] Timeout in seconds. 0 means no timeout. Default 30 minutes.
|
|
244
|
+
# @param on_progress [Proc, nil] Optional callback invoked with a
|
|
245
|
+
# +Daytona::UploadProgress+ struct as libcurl reports bytes actually uploaded.
|
|
246
|
+
# @param cancel_event [#set?, nil] Optional cancellation token. When set while
|
|
247
|
+
# staging a non-file source or during the libcurl upload, the operation raises
|
|
248
|
+
# Daytona::Sdk::Error and the in-progress upload is aborted (no destination file
|
|
249
|
+
# is left on the sandbox thanks to the daemon's atomic-rename behaviour).
|
|
250
|
+
# @return [void]
|
|
251
|
+
# @raise [Daytona::Sdk::Error] If the operation fails or +cancel_event+ is set.
|
|
252
|
+
#
|
|
253
|
+
# @example
|
|
254
|
+
# File.open("large.bin", "rb") do |f|
|
|
255
|
+
# sandbox.fs.upload_file_stream(f, "tmp/large.bin",
|
|
256
|
+
# on_progress: ->(p) { puts "#{p.bytes_sent} bytes sent" })
|
|
257
|
+
# end
|
|
258
|
+
def upload_file_stream(source, remote_path, timeout: 30 * 60, on_progress: nil, cancel_event: nil)
|
|
259
|
+
FileTransfer.stream_upload(api_client: toolbox_api.api_client, remote_path: remote_path,
|
|
260
|
+
source: source, timeout: timeout, on_progress: on_progress,
|
|
261
|
+
cancel_event: cancel_event)
|
|
262
|
+
rescue StandardError => e
|
|
263
|
+
raise Sdk::Error, "Failed to upload file: #{e.message}"
|
|
264
|
+
end
|
|
265
|
+
|
|
228
266
|
# Uploads multiple files to the Sandbox. If files already exist at the destination paths,
|
|
229
267
|
# they will be overwritten.
|
|
230
268
|
#
|
|
@@ -4,11 +4,20 @@
|
|
|
4
4
|
# frozen_string_literal: true
|
|
5
5
|
|
|
6
6
|
require 'json'
|
|
7
|
+
require 'stringio'
|
|
8
|
+
require 'tempfile'
|
|
7
9
|
require 'typhoeus'
|
|
8
10
|
|
|
9
11
|
module Daytona
|
|
12
|
+
# Progress information for a streaming download.
|
|
13
|
+
DownloadProgress = Struct.new(:bytes_received, :total_bytes, keyword_init: true)
|
|
14
|
+
|
|
15
|
+
# Progress information for a streaming upload.
|
|
16
|
+
UploadProgress = Struct.new(:bytes_sent, keyword_init: true)
|
|
17
|
+
|
|
10
18
|
class MultipartDownloadStreamParser
|
|
11
19
|
attr_reader :error_message
|
|
20
|
+
attr_reader :part_total_bytes
|
|
12
21
|
attr_writer :boundary_token
|
|
13
22
|
|
|
14
23
|
def initialize(&on_file_chunk)
|
|
@@ -17,6 +26,7 @@ module Daytona
|
|
|
17
26
|
@buffer = String.new.b
|
|
18
27
|
@state = :preamble
|
|
19
28
|
@part_name = nil
|
|
29
|
+
@part_total_bytes = nil
|
|
20
30
|
@error_buffer = String.new.b
|
|
21
31
|
end
|
|
22
32
|
|
|
@@ -62,14 +72,14 @@ module Daytona
|
|
|
62
72
|
end
|
|
63
73
|
|
|
64
74
|
def consume_headers?
|
|
65
|
-
|
|
66
|
-
index = @buffer.index(separator)
|
|
75
|
+
index = @buffer.index("\r\n\r\n".b)
|
|
67
76
|
return false unless index
|
|
68
77
|
|
|
69
78
|
headers = @buffer.byteslice(0, index)
|
|
70
|
-
@buffer = remaining_bytes(index +
|
|
71
|
-
@part_name = headers[/Content-Disposition:\s*[^\r\n]*\bname="([^"]+)"/i, 1]
|
|
72
|
-
|
|
79
|
+
@buffer = remaining_bytes(index + 4)
|
|
80
|
+
@part_name = headers[/Content-Disposition:\s*[^\r\n]*\bname="([^"]+)"/i, 1] ||
|
|
81
|
+
raise(Sdk::Error, 'Invalid multipart response')
|
|
82
|
+
@part_total_bytes = headers[/Content-Length:\s*(\d+)/i, 1]&.to_i
|
|
73
83
|
|
|
74
84
|
@state = :body
|
|
75
85
|
true
|
|
@@ -124,16 +134,11 @@ module Daytona
|
|
|
124
134
|
false
|
|
125
135
|
end
|
|
126
136
|
|
|
127
|
-
def remaining_bytes(offset)
|
|
128
|
-
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def boundary
|
|
132
|
-
"--#{@boundary_token}".b
|
|
133
|
-
end
|
|
137
|
+
def remaining_bytes(offset) = @buffer.byteslice(offset, @buffer.bytesize - offset) || String.new.b
|
|
138
|
+
def boundary = "--#{@boundary_token}".b
|
|
134
139
|
end
|
|
135
140
|
|
|
136
|
-
module FileTransfer
|
|
141
|
+
module FileTransfer # rubocop:disable Metrics/ModuleLength
|
|
137
142
|
def self.extract_multipart_boundary(content_type)
|
|
138
143
|
match = content_type&.match(/boundary=(?:"([^"]+)"|([^;]+))/i)
|
|
139
144
|
return unless match
|
|
@@ -141,9 +146,31 @@ module Daytona
|
|
|
141
146
|
match.captures.compact.first
|
|
142
147
|
end
|
|
143
148
|
|
|
144
|
-
def self.
|
|
149
|
+
def self.assign_download_boundary(parser, content_type)
|
|
150
|
+
boundary = extract_multipart_boundary(content_type)
|
|
151
|
+
raise Sdk::Error, 'Missing multipart boundary in download response' unless boundary
|
|
152
|
+
|
|
153
|
+
parser.boundary_token = boundary
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
157
|
+
def self.stream_download(api_client:, remote_path:, timeout:, on_progress: nil, cancel_event: nil, &block)
|
|
145
158
|
config = api_client.config
|
|
146
|
-
|
|
159
|
+
bytes_received = 0
|
|
160
|
+
parser = nil
|
|
161
|
+
wrapped_block = proc do |chunk|
|
|
162
|
+
raise Sdk::Error, "Download cancelled: #{remote_path}" if cancel_event&.set?
|
|
163
|
+
|
|
164
|
+
if on_progress
|
|
165
|
+
bytes_received += chunk.bytesize
|
|
166
|
+
on_progress.call(DownloadProgress.new(
|
|
167
|
+
bytes_received: bytes_received,
|
|
168
|
+
total_bytes: parser&.part_total_bytes
|
|
169
|
+
))
|
|
170
|
+
end
|
|
171
|
+
block.call(chunk)
|
|
172
|
+
end
|
|
173
|
+
parser = MultipartDownloadStreamParser.new(&wrapped_block)
|
|
147
174
|
response = nil
|
|
148
175
|
|
|
149
176
|
request = Typhoeus::Request.new(
|
|
@@ -160,13 +187,15 @@ module Daytona
|
|
|
160
187
|
)
|
|
161
188
|
|
|
162
189
|
request.on_headers do |stream_response|
|
|
163
|
-
|
|
164
|
-
raise Sdk::Error, 'Missing multipart boundary in download response' unless boundary
|
|
165
|
-
|
|
166
|
-
parser.boundary_token = boundary
|
|
190
|
+
assign_download_boundary(parser, stream_response.headers['Content-Type'])
|
|
167
191
|
end
|
|
168
192
|
|
|
193
|
+
# Returning +:abort+ from the on_body callback tells libcurl to tear down the
|
|
194
|
+
# connection immediately, which is how cancellation actually severs the
|
|
195
|
+
# transfer rather than just stopping our own bookkeeping.
|
|
169
196
|
request.on_body do |chunk|
|
|
197
|
+
next :abort if cancel_event&.set?
|
|
198
|
+
|
|
170
199
|
parser << chunk
|
|
171
200
|
end
|
|
172
201
|
|
|
@@ -177,8 +206,143 @@ module Daytona
|
|
|
177
206
|
|
|
178
207
|
request.run
|
|
179
208
|
|
|
209
|
+
raise Sdk::Error, "Download cancelled: #{remote_path}" if cancel_event&.set?
|
|
180
210
|
raise Sdk::Error, parser.error_message if parser.error_message
|
|
181
211
|
raise Sdk::Error, "HTTP #{response.code}" if response && !response.success?
|
|
182
212
|
end
|
|
213
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
214
|
+
|
|
215
|
+
# Uploads +source+ to /files/bulk-upload via Typhoeus (libcurl), which streams the
|
|
216
|
+
# request body straight from disk without buffering it in memory. Local file paths
|
|
217
|
+
# are uploaded directly; in-memory IOs/bytes are first drained to a tempfile so we
|
|
218
|
+
# have a stable file handle for libcurl.
|
|
219
|
+
#
|
|
220
|
+
# The daemon owns atomicity (writes to a sibling tempfile then renames), so a
|
|
221
|
+
# client-side abort just leaves no destination file at all.
|
|
222
|
+
#
|
|
223
|
+
# @param api_client The OpenAPI-generated toolbox API client (auth/base-url only).
|
|
224
|
+
# @param remote_path [String] Destination path in the sandbox.
|
|
225
|
+
# @param source [String, IO] Local file path or any IO-like object responding to +read(n)+.
|
|
226
|
+
# @param timeout [Integer] Typhoeus timeout in seconds (0 disables).
|
|
227
|
+
# @param on_progress [Proc, nil] Optional callback invoked with +Daytona::UploadProgress+
|
|
228
|
+
# as libcurl reports real network upload progress.
|
|
229
|
+
# @param cancel_event [#set?, nil] Optional cancellation token. Checked while staging
|
|
230
|
+
# non-file sources and during the libcurl transfer itself.
|
|
231
|
+
# rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
|
|
232
|
+
def self.stream_upload(api_client:, remote_path:, source:, timeout:, on_progress: nil, cancel_event: nil)
|
|
233
|
+
with_upload_file(source, cancel_event, remote_path) do |upload_path|
|
|
234
|
+
config = api_client.config
|
|
235
|
+
progress_callback = upload_progress_callback(on_progress, cancel_event)
|
|
236
|
+
response = with_open_upload_file(upload_path) do |file|
|
|
237
|
+
upload_request(
|
|
238
|
+
api_client: api_client,
|
|
239
|
+
config: config,
|
|
240
|
+
remote_path: remote_path,
|
|
241
|
+
file: file,
|
|
242
|
+
timeout: timeout,
|
|
243
|
+
progress_callback: progress_callback
|
|
244
|
+
).run
|
|
245
|
+
end
|
|
246
|
+
raise_upload_error(response, cancel_event, remote_path)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
# rubocop:enable Metrics/MethodLength, Metrics/ParameterLists
|
|
250
|
+
|
|
251
|
+
# Yields a path on disk that holds the source's bytes, ready for libcurl to stream.
|
|
252
|
+
# Local files are passed through unchanged; everything else is drained into a
|
|
253
|
+
# tempfile that gets unlinked when we return.
|
|
254
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
255
|
+
def self.with_upload_file(source, cancel_event, remote_path)
|
|
256
|
+
raise Sdk::Error, "Upload cancelled: #{remote_path}" if cancel_event&.set?
|
|
257
|
+
|
|
258
|
+
return yield(source) if source.is_a?(String) && File.exist?(source)
|
|
259
|
+
|
|
260
|
+
tmp = Tempfile.new(['daytona-upload-', File.extname(remote_path).to_s])
|
|
261
|
+
tmp.binmode
|
|
262
|
+
begin
|
|
263
|
+
drain_source_to(source, tmp, cancel_event, remote_path)
|
|
264
|
+
tmp.flush
|
|
265
|
+
tmp.close
|
|
266
|
+
yield(tmp.path)
|
|
267
|
+
ensure
|
|
268
|
+
tmp.close unless tmp.closed?
|
|
269
|
+
begin
|
|
270
|
+
tmp.unlink
|
|
271
|
+
rescue StandardError
|
|
272
|
+
# tempfile already gone, nothing to do
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
277
|
+
|
|
278
|
+
def self.drain_source_to(source, sink, cancel_event, remote_path)
|
|
279
|
+
io, owns_io = open_drain_source(source)
|
|
280
|
+
begin
|
|
281
|
+
while (chunk = io.read(64 * 1024))
|
|
282
|
+
break if chunk.empty?
|
|
283
|
+
raise Sdk::Error, "Upload cancelled: #{remote_path}" if cancel_event&.set?
|
|
284
|
+
|
|
285
|
+
sink.write(chunk)
|
|
286
|
+
end
|
|
287
|
+
ensure
|
|
288
|
+
io.close if owns_io && io.respond_to?(:close)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def self.with_open_upload_file(upload_path)
|
|
293
|
+
file = File.open(upload_path, 'rb')
|
|
294
|
+
yield(file)
|
|
295
|
+
ensure
|
|
296
|
+
file.close if file && !file.closed?
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
|
|
300
|
+
def self.upload_request(api_client:, config:, remote_path:, file:, timeout:, progress_callback:)
|
|
301
|
+
Typhoeus::Request.new(
|
|
302
|
+
"#{config.base_url}/files/bulk-upload",
|
|
303
|
+
method: :post,
|
|
304
|
+
headers: api_client.default_headers.dup.tap { |h| h.delete('Content-Type') },
|
|
305
|
+
body: {
|
|
306
|
+
'files[0].path' => remote_path,
|
|
307
|
+
'files[0].file' => file
|
|
308
|
+
},
|
|
309
|
+
timeout: timeout,
|
|
310
|
+
ssl_verifypeer: config.verify_ssl,
|
|
311
|
+
ssl_verifyhost: config.verify_ssl_host ? 2 : 0,
|
|
312
|
+
noprogress: false,
|
|
313
|
+
progressfunction: progress_callback,
|
|
314
|
+
xferinfofunction: progress_callback
|
|
315
|
+
)
|
|
316
|
+
end
|
|
317
|
+
# rubocop:enable Metrics/MethodLength, Metrics/ParameterLists
|
|
318
|
+
|
|
319
|
+
def self.upload_progress_callback(on_progress, cancel_event)
|
|
320
|
+
last_bytes_sent = -1
|
|
321
|
+
|
|
322
|
+
proc do |_clientp, _dltotal, _dlnow, _ultotal, ulnow|
|
|
323
|
+
next 1 if cancel_event&.set?
|
|
324
|
+
|
|
325
|
+
bytes_sent = ulnow.to_i
|
|
326
|
+
if on_progress && bytes_sent > last_bytes_sent
|
|
327
|
+
last_bytes_sent = bytes_sent
|
|
328
|
+
on_progress.call(UploadProgress.new(bytes_sent: bytes_sent))
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
0
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def self.raise_upload_error(response, _cancel_event, remote_path)
|
|
336
|
+
raise Sdk::Error, "Upload timed out: #{remote_path}" if response.timed_out?
|
|
337
|
+
raise Sdk::Error, "Upload cancelled: #{remote_path}" if response.return_code == :aborted_by_callback
|
|
338
|
+
raise Sdk::Error, "HTTP #{response.code}: #{response.body}" unless response.success?
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def self.open_drain_source(source)
|
|
342
|
+
return [source, false] if source.respond_to?(:read)
|
|
343
|
+
return [StringIO.new(source.b), true] if source.is_a?(String)
|
|
344
|
+
|
|
345
|
+
raise Sdk::Error, "Unsupported upload source: #{source.class}"
|
|
346
|
+
end
|
|
183
347
|
end
|
|
184
348
|
end
|
data/lib/daytona/git.rb
CHANGED
|
@@ -45,6 +45,8 @@ module Daytona
|
|
|
45
45
|
# ])
|
|
46
46
|
def add(path, files)
|
|
47
47
|
toolbox_api.add_files(DaytonaToolboxApiClient::GitAddRequest.new(path:, files:))
|
|
48
|
+
rescue DaytonaToolboxApiClient::ApiError => e
|
|
49
|
+
raise map_api_error(e, 'Failed to add files')
|
|
48
50
|
rescue StandardError => e
|
|
49
51
|
raise Sdk::Error, "Failed to add files: #{e.message}"
|
|
50
52
|
end
|
|
@@ -61,6 +63,8 @@ module Daytona
|
|
|
61
63
|
# puts "Branches: #{response.branches}"
|
|
62
64
|
def branches(path)
|
|
63
65
|
toolbox_api.list_branches(path)
|
|
66
|
+
rescue DaytonaToolboxApiClient::ApiError => e
|
|
67
|
+
raise map_api_error(e, 'Failed to list branches')
|
|
64
68
|
rescue StandardError => e
|
|
65
69
|
raise Sdk::Error, "Failed to list branches: #{e.message}"
|
|
66
70
|
end
|
|
@@ -114,6 +118,8 @@ module Daytona
|
|
|
114
118
|
commit_id: commit_id
|
|
115
119
|
)
|
|
116
120
|
)
|
|
121
|
+
rescue DaytonaToolboxApiClient::ApiError => e
|
|
122
|
+
raise map_api_error(e, 'Failed to clone repository')
|
|
117
123
|
rescue StandardError => e
|
|
118
124
|
raise Sdk::Error, "Failed to clone repository: #{e.message}"
|
|
119
125
|
end
|
|
@@ -146,6 +152,8 @@ module Daytona
|
|
|
146
152
|
DaytonaToolboxApiClient::GitCommitRequest.new(path:, message:, author:, email:, allow_empty:)
|
|
147
153
|
)
|
|
148
154
|
GitCommitResponse.new(sha: response._hash)
|
|
155
|
+
rescue DaytonaToolboxApiClient::ApiError => e
|
|
156
|
+
raise map_api_error(e, 'Failed to commit changes')
|
|
149
157
|
rescue StandardError => e
|
|
150
158
|
raise Sdk::Error, "Failed to commit changes: #{e.message}"
|
|
151
159
|
end
|
|
@@ -175,6 +183,8 @@ module Daytona
|
|
|
175
183
|
toolbox_api.push_changes(
|
|
176
184
|
DaytonaToolboxApiClient::GitRepoRequest.new(path:, username:, password:)
|
|
177
185
|
)
|
|
186
|
+
rescue DaytonaToolboxApiClient::ApiError => e
|
|
187
|
+
raise map_api_error(e, 'Failed to push changes')
|
|
178
188
|
rescue StandardError => e
|
|
179
189
|
raise Sdk::Error, "Failed to push changes: #{e.message}"
|
|
180
190
|
end
|
|
@@ -204,6 +214,8 @@ module Daytona
|
|
|
204
214
|
toolbox_api.pull_changes(
|
|
205
215
|
DaytonaToolboxApiClient::GitRepoRequest.new(path:, username:, password:)
|
|
206
216
|
)
|
|
217
|
+
rescue DaytonaToolboxApiClient::ApiError => e
|
|
218
|
+
raise map_api_error(e, 'Failed to pull changes')
|
|
207
219
|
rescue StandardError => e
|
|
208
220
|
raise Sdk::Error, "Failed to pull changes: #{e.message}"
|
|
209
221
|
end
|
|
@@ -222,6 +234,8 @@ module Daytona
|
|
|
222
234
|
# puts "Commits behind: #{status.behind}"
|
|
223
235
|
def status(path)
|
|
224
236
|
toolbox_api.get_status(path)
|
|
237
|
+
rescue DaytonaToolboxApiClient::ApiError => e
|
|
238
|
+
raise map_api_error(e, 'Failed to get status')
|
|
225
239
|
rescue StandardError => e
|
|
226
240
|
raise Sdk::Error, "Failed to get status: #{e.message}"
|
|
227
241
|
end
|
|
@@ -241,6 +255,8 @@ module Daytona
|
|
|
241
255
|
toolbox_api.checkout_branch(
|
|
242
256
|
DaytonaToolboxApiClient::GitCheckoutRequest.new(path:, branch:)
|
|
243
257
|
)
|
|
258
|
+
rescue DaytonaToolboxApiClient::ApiError => e
|
|
259
|
+
raise map_api_error(e, 'Failed to checkout branch')
|
|
244
260
|
rescue StandardError => e
|
|
245
261
|
raise Sdk::Error, "Failed to checkout branch: #{e.message}"
|
|
246
262
|
end
|
|
@@ -261,6 +277,8 @@ module Daytona
|
|
|
261
277
|
toolbox_api.create_branch(
|
|
262
278
|
DaytonaToolboxApiClient::GitBranchRequest.new(path:, name:)
|
|
263
279
|
)
|
|
280
|
+
rescue DaytonaToolboxApiClient::ApiError => e
|
|
281
|
+
raise map_api_error(e, 'Failed to create branch')
|
|
264
282
|
rescue StandardError => e
|
|
265
283
|
raise Sdk::Error, "Failed to create branch: #{e.message}"
|
|
266
284
|
end
|
|
@@ -280,6 +298,8 @@ module Daytona
|
|
|
280
298
|
toolbox_api.delete_branch(
|
|
281
299
|
DaytonaToolboxApiClient::GitDeleteBranchRequest.new(path:, name:)
|
|
282
300
|
)
|
|
301
|
+
rescue DaytonaToolboxApiClient::ApiError => e
|
|
302
|
+
raise map_api_error(e, 'Failed to delete branch')
|
|
283
303
|
rescue StandardError => e
|
|
284
304
|
raise Sdk::Error, "Failed to delete branch: #{e.message}"
|
|
285
305
|
end
|
|
@@ -292,5 +312,19 @@ module Daytona
|
|
|
292
312
|
|
|
293
313
|
# @return [Daytona::OtelState, nil]
|
|
294
314
|
attr_reader :otel_state
|
|
315
|
+
|
|
316
|
+
def map_api_error(api_error, prefix)
|
|
317
|
+
msg = "#{prefix}: #{api_error.message}"
|
|
318
|
+
case api_error.code
|
|
319
|
+
when 400 then Sdk::ValidationError.new(msg)
|
|
320
|
+
when 401 then Sdk::AuthenticationError.new(msg)
|
|
321
|
+
when 403 then Sdk::ForbiddenError.new(msg)
|
|
322
|
+
when 404 then Sdk::NotFoundError.new(msg)
|
|
323
|
+
when 409 then Sdk::ConflictError.new(msg)
|
|
324
|
+
when 429 then Sdk::RateLimitError.new(msg)
|
|
325
|
+
when 500..599 then Sdk::ServerError.new(msg)
|
|
326
|
+
else Sdk::Error.new(msg)
|
|
327
|
+
end
|
|
328
|
+
end
|
|
295
329
|
end
|
|
296
330
|
end
|
data/lib/daytona/sdk/version.rb
CHANGED
data/lib/daytona/sdk.rb
CHANGED
|
@@ -43,6 +43,13 @@ module Daytona
|
|
|
43
43
|
module Sdk
|
|
44
44
|
class Error < StandardError; end
|
|
45
45
|
class TimeoutError < Error; end
|
|
46
|
+
class AuthenticationError < Error; end
|
|
47
|
+
class ForbiddenError < Error; end
|
|
48
|
+
class NotFoundError < Error; end
|
|
49
|
+
class ConflictError < Error; end
|
|
50
|
+
class ValidationError < Error; end
|
|
51
|
+
class RateLimitError < Error; end
|
|
52
|
+
class ServerError < Error; end
|
|
46
53
|
|
|
47
54
|
def self.logger = @logger ||= Logger.new($stdout, level: Logger::INFO)
|
|
48
55
|
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.
|
|
4
|
+
version: 0.175.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daytona Platforms Inc.
|
|
@@ -85,28 +85,28 @@ dependencies:
|
|
|
85
85
|
requirements:
|
|
86
86
|
- - '='
|
|
87
87
|
- !ruby/object:Gem::Version
|
|
88
|
-
version: 0.
|
|
88
|
+
version: 0.175.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.175.0
|
|
96
96
|
- !ruby/object:Gem::Dependency
|
|
97
97
|
name: daytona_toolbox_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.175.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.175.0
|
|
110
110
|
- !ruby/object:Gem::Dependency
|
|
111
111
|
name: dotenv
|
|
112
112
|
requirement: !ruby/object:Gem::Requirement
|