daytona 0.202.0 → 0.204.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: 0d211705ef263fa492b31a4e32ffc3a1d6ff3dbd2429baa2fc03dcc60012672b
4
- data.tar.gz: 803fc638b4d11ac3c2435621f565967888ac68e1278081f382ad9977ec26aa94
3
+ metadata.gz: b9262546e49c3718967e3a0300303d4a29307bf48e05d3858adbbd64ed3c5476
4
+ data.tar.gz: f7292a5cb930340df80595e497782a28fab1beb9bb3bbb5b8147a52ed8a4c952
5
5
  SHA512:
6
- metadata.gz: a73eb35713a3d7bc507b9f5ef8290a2230c9accc2efd17dc2c6d6a9dc452ce1ac12d1453caba8c61e175743b488e22ad4876be8093a6208465c5071b33844b5e
7
- data.tar.gz: dbf474763c6e4254eba6bd65d7892a756879fd22f10401eb3576fc1ccb4b89c4fe4beeaa45bb6e611de1346a5507134e6c11a8f028991159f6f6cd60b4592231
6
+ metadata.gz: 5ea724b9fd64d2efc919c764b7236b008e4eeda909b1796d0c05b91e31085cc379fe7a8215949ec1d0f8de40851a045edffd9aaabdfa13767d2346567876ef18
7
+ data.tar.gz: 44b45171c7a2d21e1060c52f85ef90a9379d10ddaeaf5bd4b66ecd3dd097ba6cfe1b67ec86e8feb00cdd96bd25853ef82e8bcd35b53db3e395fcc39b04199420
data/README.md CHANGED
@@ -6,12 +6,20 @@ The SDK provides an interface for sandbox management, file system operations, Gi
6
6
 
7
7
  ## Installation
8
8
 
9
+ Requires **Ruby >= 3.2**. Check your version with `ruby -v` before installing — on older versions the install fails with a Bundler/RubyGems resolver error rather than a clear message.
10
+
9
11
  Install the package using **gem**:
10
12
 
11
13
  ```bash
12
14
  gem install daytona
13
15
  ```
14
16
 
17
+ or add it to your **Gemfile**:
18
+
19
+ ```ruby
20
+ gem 'daytona'
21
+ ```
22
+
15
23
  ## Get API key
16
24
 
17
25
  Generate an API key from the [Daytona Dashboard ↗](https://app.daytona.io/dashboard/keys) to authenticate SDK requests and access Daytona services. For more information, see the [API keys](https://www.daytona.io/docs/en/api-keys/) documentation.
@@ -182,3 +190,45 @@ completions = lsp_server.completions(
182
190
  position: Daytona::LspServer::Position.new(line: 10, character: 15)
183
191
  )
184
192
  ```
193
+
194
+ ## List method return shapes
195
+
196
+ Each `list` method returns a different shape depending on the resource. The table below shows the exact return type and how to access the elements.
197
+
198
+ | Method | Return type | Shape | Access elements |
199
+ | --- | --- | --- | --- |
200
+ | `daytona.snapshot.list(page:, limit:)` | `Daytona::PaginatedResource` | Paginated wrapper | `result.items.each` |
201
+ | `daytona.secret.list(cursor:, limit:, ...)` | `Daytona::ListSecretsResponse` | Cursor-paginated wrapper | `page.items.each` |
202
+ | `daytona.volume.list` | `Array<Daytona::Volume>` | Bare array | iterate directly |
203
+ | `daytona.list(query)` | `Enumerator<Daytona::Sandbox>` | Lazy enumerator | `.each { \|s\| ... }` |
204
+
205
+ `PaginatedResource` and `ListSecretsResponse` are **wrapper objects**, not arrays. Calling `.each` or `.map` directly on them raises `NoMethodError`. Always go through `.items`:
206
+
207
+ ```ruby
208
+ # snapshots — page-number pagination
209
+ result = daytona.snapshot.list(page: 1, limit: 20)
210
+ # result.items => Array<Daytona::Snapshot>
211
+ # result.total => Float (total across all pages)
212
+ # result.page => Float (current page, 1-indexed)
213
+ # result.total_pages => Float
214
+ result.items.each { |snapshot| puts "#{snapshot.name} (#{snapshot.image_name})" }
215
+
216
+ # secrets — cursor pagination
217
+ cursor = nil
218
+ loop do
219
+ page = daytona.secret.list(cursor: cursor, limit: 100)
220
+ # page.items => Array<Daytona::Secret>
221
+ # page.total => Float
222
+ # page.next_cursor => String | nil (nil = no more pages)
223
+ page.items.each { |secret| puts secret.name }
224
+ cursor = page.next_cursor
225
+ break if cursor.nil?
226
+ end
227
+
228
+ # volumes — bare array, iterate directly
229
+ daytona.volume.list.each { |vol| puts vol.name }
230
+
231
+ # sandboxes — lazy enumerator, fetches pages on demand
232
+ query = Daytona::ListSandboxesQuery.new(labels: { 'env' => 'dev' })
233
+ daytona.list(query).each { |sandbox| puts sandbox.id }
234
+ ```
@@ -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 [String, nil] Outbound proxy URL to route the Sandbox HTTP(S) traffic through
60
+ attr_accessor :outbound_proxy_url
61
+
59
62
  # @return [Integer, nil] Time to live in minutes (0 to disable)
60
63
  attr_accessor :ttl_minutes
61
64
 
@@ -86,6 +89,9 @@ module Daytona
86
89
  # @param network_block_all [Boolean, nil] Whether to block all network access for the Sandbox
87
90
  # @param network_allow_list [String, nil] Comma-separated list of allowed CIDR network addresses for the Sandbox
88
91
  # @param domain_allow_list [String, nil] Comma-separated list of allowed domains for the Sandbox
92
+ # @param outbound_proxy_url [String, nil] Outbound proxy URL to route the Sandbox HTTP(S) traffic through.
93
+ # Applied via the HTTP(S)_PROXY environment variables; combine with domain_allow_list for
94
+ # network-layer enforcement.
89
95
  # @param ttl_minutes [Integer, nil] Time to live in minutes (0 to disable)
90
96
  # @param ephemeral [Boolean, nil] Whether the Sandbox should be ephemeral
91
97
  # @param linked_sandbox [String, nil] ID or name of an existing Sandbox to link the new Sandbox to
@@ -106,6 +112,7 @@ module Daytona
106
112
  network_block_all: nil,
107
113
  network_allow_list: nil,
108
114
  domain_allow_list: nil,
115
+ outbound_proxy_url: nil,
109
116
  ephemeral: nil,
110
117
  linked_sandbox: nil
111
118
  )
@@ -125,6 +132,7 @@ module Daytona
125
132
  @network_block_all = network_block_all
126
133
  @network_allow_list = network_allow_list
127
134
  @domain_allow_list = domain_allow_list
135
+ @outbound_proxy_url = outbound_proxy_url
128
136
  @ephemeral = ephemeral
129
137
  @linked_sandbox = linked_sandbox
130
138
 
@@ -153,6 +161,7 @@ module Daytona
153
161
  network_block_all:,
154
162
  network_allow_list:,
155
163
  domain_allow_list:,
164
+ outbound_proxy_url:,
156
165
  ephemeral:,
157
166
  linked_sandbox:
158
167
  }.compact
@@ -207,6 +216,9 @@ module Daytona
207
216
  # @param network_block_all [Boolean, nil] Whether to block all network access for the Sandbox
208
217
  # @param network_allow_list [String, nil] Comma-separated list of allowed CIDR network addresses for the Sandbox
209
218
  # @param domain_allow_list [String, nil] Comma-separated list of allowed domains for the Sandbox
219
+ # @param outbound_proxy_url [String, nil] Outbound proxy URL to route the Sandbox HTTP(S) traffic through.
220
+ # Applied via the HTTP(S)_PROXY environment variables; combine with domain_allow_list for
221
+ # network-layer enforcement.
210
222
  # @param ephemeral [Boolean, nil] Whether the Sandbox should be ephemeral
211
223
  def initialize(image:, resources: nil, **args)
212
224
  @image = image
@@ -250,6 +262,9 @@ module Daytona
250
262
  # @param network_block_all [Boolean, nil] Whether to block all network access for the Sandbox
251
263
  # @param network_allow_list [String, nil] Comma-separated list of allowed CIDR network addresses for the Sandbox
252
264
  # @param domain_allow_list [String, nil] Comma-separated list of allowed domains for the Sandbox
265
+ # @param outbound_proxy_url [String, nil] Outbound proxy URL to route the Sandbox HTTP(S) traffic through.
266
+ # Applied via the HTTP(S)_PROXY environment variables; combine with domain_allow_list for
267
+ # network-layer enforcement.
253
268
  # @param ephemeral [Boolean, nil] Whether the Sandbox should be ephemeral
254
269
  def initialize(snapshot: nil, **args)
255
270
  @snapshot = snapshot
@@ -43,6 +43,14 @@ module Daytona
43
43
  end
44
44
 
45
45
  class Snapshot
46
+ # Matches RFC 4122 UUIDs (versions 1-5) and the nil UUID - the same set the
47
+ # Daytona API recognizes as snapshot IDs. Anything else is treated as a name.
48
+ SNAPSHOT_ID_PATTERN = Regexp.new(
49
+ '\A(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}' \
50
+ '|00000000-0000-0000-0000-000000000000)\z',
51
+ Regexp::IGNORECASE
52
+ ).freeze
53
+
46
54
  # @return [String] Unique identifier for the Snapshot
47
55
  attr_reader :id
48
56
 
@@ -94,6 +102,9 @@ module Daytona
94
102
  # @return [DaytonaApiClient::BuildInfo, nil] Build information for the snapshot
95
103
  attr_reader :build_info
96
104
 
105
+ # @return [String, nil] ID of the sandbox the Snapshot was created from
106
+ attr_reader :source_sandbox_id
107
+
97
108
  # @param snapshot_dto [DaytonaApiClient::SnapshotDto] The snapshot DTO from the API
98
109
  def initialize(snapshot_dto) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
99
110
  @id = snapshot_dto.id
@@ -113,6 +124,7 @@ module Daytona
113
124
  @updated_at = snapshot_dto.updated_at
114
125
  @last_used_at = snapshot_dto.last_used_at
115
126
  @build_info = snapshot_dto.build_info
127
+ @source_sandbox_id = snapshot_dto.source_sandbox_id
116
128
  end
117
129
 
118
130
  # Creates a Snapshot instance from a SnapshotDto
@@ -120,5 +132,11 @@ module Daytona
120
132
  # @param dto [DaytonaApiClient::SnapshotDto] The snapshot DTO from the API
121
133
  # @return [Daytona::Snapshot] The snapshot instance
122
134
  def self.from_dto(dto) = new(dto)
135
+
136
+ # Whether the given value looks like a Snapshot ID (a UUID) rather than a name.
137
+ #
138
+ # @param value [Object] value to test
139
+ # @return [Boolean] true when value is a UUID-shaped String
140
+ def self.id?(value) = value.is_a?(String) && SNAPSHOT_ID_PATTERN.match?(value)
123
141
  end
124
142
  end
@@ -289,6 +289,7 @@ module Daytona
289
289
  network_block_all: params.network_block_all,
290
290
  network_allow_list: params.network_allow_list,
291
291
  domain_allow_list: params.domain_allow_list,
292
+ outbound_proxy_url: params.outbound_proxy_url,
292
293
  linked_sandbox: params.linked_sandbox
293
294
  )
294
295
 
@@ -88,6 +88,10 @@ module Daytona
88
88
  # Not returned by list results; call #refresh on each item to populate.
89
89
  attr_reader :domain_allow_list
90
90
 
91
+ # @return [String, nil] Outbound proxy URL to route the sandbox HTTP(S) traffic through.
92
+ # Not returned by list results; call #refresh on each item to populate.
93
+ attr_reader :outbound_proxy_url
94
+
91
95
  # @return [String] The target environment for the sandbox
92
96
  attr_reader :target
93
97
 
@@ -1109,6 +1113,7 @@ module Daytona
1109
1113
  @network_block_all = sandbox_dto.network_block_all
1110
1114
  @network_allow_list = sandbox_dto.network_allow_list
1111
1115
  @domain_allow_list = sandbox_dto.domain_allow_list
1116
+ @outbound_proxy_url = sandbox_dto.outbound_proxy_url
1112
1117
  @volumes = sandbox_dto.volumes
1113
1118
  @build_info = sandbox_dto.build_info
1114
1119
  @backup_created_at = sandbox_dto.backup_created_at
@@ -120,11 +120,19 @@ module Daytona
120
120
  class GitPushRejectedError < ConflictError; end
121
121
  class GitDirtyWorktreeError < ConflictError; end
122
122
  class GitMergeConflictError < ConflictError; end
123
+ class GitTransportFailedError < BadGatewayError; end
124
+ class GitRemoteRejectedError < UnprocessableEntityError; end
123
125
 
124
126
  # Daemon: filesystem
125
127
  class FileNotFoundError < NotFoundError; end
126
128
  class FileAccessDeniedError < ForbiddenError; end
127
129
 
130
+ # The supplied file path was rejected by the daemon (code `INVALID_FILE_PATH`).
131
+ class InvalidFilePathError < BadRequestError; end
132
+
133
+ # The daemon could not read the sandbox file (code `FILE_READ_FAILED`).
134
+ class FileReadFailedError < InternalServerError; end
135
+
128
136
  # Daemon: LSP
129
137
  class LspServerNotInitializedError < BadRequestError; end
130
138
 
@@ -171,10 +179,14 @@ module Daytona
171
179
  [SOURCE_DAEMON, 'GIT_PUSH_REJECTED'] => GitPushRejectedError,
172
180
  [SOURCE_DAEMON, 'GIT_DIRTY_WORKTREE'] => GitDirtyWorktreeError,
173
181
  [SOURCE_DAEMON, 'GIT_MERGE_CONFLICT'] => GitMergeConflictError,
182
+ [SOURCE_DAEMON, 'GIT_TRANSPORT_FAILED'] => GitTransportFailedError,
183
+ [SOURCE_DAEMON, 'GIT_REMOTE_REJECTED'] => GitRemoteRejectedError,
174
184
 
175
185
  # Daemon: filesystem
176
186
  [SOURCE_DAEMON, 'FILE_NOT_FOUND'] => FileNotFoundError,
177
187
  [SOURCE_DAEMON, 'FILE_ACCESS_DENIED'] => FileAccessDeniedError,
188
+ [SOURCE_DAEMON, 'INVALID_FILE_PATH'] => InvalidFilePathError,
189
+ [SOURCE_DAEMON, 'FILE_READ_FAILED'] => FileReadFailedError,
178
190
 
179
191
  # Daemon: LSP
180
192
  [SOURCE_DAEMON, 'LSP_SERVER_NOT_INITIALIZED'] => LspServerNotInitializedError,
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Daytona
7
7
  module Sdk
8
- VERSION = '0.202.0'
8
+ VERSION = '0.204.0'
9
9
  end
10
10
  end
@@ -66,7 +66,8 @@ module Daytona
66
66
  # cursor = nil
67
67
  # loop do
68
68
  # page = daytona.secret.list(cursor:, limit: 100)
69
- # page.items.each { |secret| puts secret.name }
69
+ # puts "Fetched #{page.items.length} of #{page.total} secrets"
70
+ # page.items.each { |secret| puts "#{secret.name} (#{secret.id})" }
70
71
  # cursor = page.next_cursor
71
72
  # break if cursor.nil?
72
73
  # end
@@ -26,19 +26,21 @@ module Daytona
26
26
  #
27
27
  # @param page [Integer, Nil]
28
28
  # @param limit [Integer, Nil]
29
+ # @param source_sandbox_id [String, Nil] Filter by the ID of the sandbox the snapshot was created from
29
30
  # @return [Daytona::PaginatedResource] Paginated list of all Snapshots
30
31
  # @raise [Daytona::Sdk::Error]
31
32
  #
32
33
  # @example
33
34
  # daytona = Daytona::Daytona.new
34
- # response = daytona.snapshot.list(page: 1, limit: 10)
35
- # snapshots.items.each { |snapshot| puts "#{snapshot.name} (#{snapshot.image_name})" }
36
- def list(page: nil, limit: nil)
35
+ # page = daytona.snapshot.list(page: 2, limit: 10)
36
+ # puts "Page #{page.page} of #{page.total_pages} (#{page.total} snapshots total)"
37
+ # page.items.each { |snapshot| puts "#{snapshot.name} (#{snapshot.image_name})" }
38
+ def list(page: nil, limit: nil, source_sandbox_id: nil)
37
39
  raise Sdk::Error, 'page must be positive integer' if page && page < 1
38
40
 
39
41
  raise Sdk::Error, 'limit must be positive integer' if limit && limit < 1
40
42
 
41
- response = snapshots_api.get_all_snapshots(page:, limit:)
43
+ response = snapshots_api.get_all_snapshots(page:, limit:, source_sandbox_id:)
42
44
  PaginatedResource.new(
43
45
  total: response.total,
44
46
  page: response.page,
@@ -49,7 +51,10 @@ module Daytona
49
51
 
50
52
  # Delete a Snapshot.
51
53
  #
52
- # @param snapshot [Daytona::Snapshot] Snapshot to delete
54
+ # @param snapshot [Daytona::Snapshot, String] Snapshot to delete, or its ID or name.
55
+ # Call cost: 1 API call for a Snapshot object or UUID string; 2 for a name
56
+ # (resolve, then delete); up to 3 in the worst case for a UUID-formatted name
57
+ # (the optimistic delete 404s, then resolve + delete).
53
58
  # @return [void]
54
59
  #
55
60
  # @example
@@ -57,11 +62,13 @@ module Daytona
57
62
  # snapshot = daytona.snapshot.get("demo")
58
63
  # daytona.snapshot.delete(snapshot)
59
64
  # puts "Snapshot deleted"
60
- def delete(snapshot) = snapshots_api.remove_snapshot(snapshot.id)
65
+ def delete(snapshot)
66
+ call_with_resolved_id(snapshot) { |id| snapshots_api.remove_snapshot(id) }
67
+ end
61
68
 
62
- # Get a Snapshot by name.
69
+ # Get a Snapshot by ID or name.
63
70
  #
64
- # @param name [String] Name of the Snapshot to get
71
+ # @param name [String] ID or name of the Snapshot to get
65
72
  # @return [Daytona::Snapshot] The Snapshot object
66
73
  #
67
74
  # @example
@@ -125,11 +132,15 @@ module Daytona
125
132
  Snapshot.from_dto(snapshot)
126
133
  end
127
134
 
128
- # Activate a snapshot
135
+ # Activate a snapshot.
129
136
  #
130
- # @param snapshot [Daytona::Snapshot] The snapshot instance
137
+ # @param snapshot [Daytona::Snapshot, String] The Snapshot instance, or its ID or name.
138
+ # Call cost matches `delete`: 1 for an object or UUID string, 2 for a name,
139
+ # up to 3 for a UUID-formatted name (optimistic 404 then resolve + activate).
131
140
  # @return [Daytona::Snapshot]
132
- def activate(snapshot) = Snapshot.from_dto(snapshots_api.activate_snapshot(snapshot.id))
141
+ def activate(snapshot)
142
+ Snapshot.from_dto(call_with_resolved_id(snapshot) { |id| snapshots_api.activate_snapshot(id) })
143
+ end
133
144
 
134
145
  instrument :list, :delete, :get, :create, :activate, component: 'SnapshotService'
135
146
 
@@ -174,6 +185,30 @@ module Daytona
174
185
  # @return [Daytona::OtelState, nil]
175
186
  attr_reader :otel_state
176
187
 
188
+ # Invoke an ID-based Snapshot operation, resolving the given identifier with as
189
+ # few API calls as possible. Snapshot names may themselves be UUID-formatted, so
190
+ # a UUID-shaped string is first tried directly as an ID (single call) and only
191
+ # resolved through the API on a 404. Any other string costs one resolution call.
192
+ #
193
+ # @param snapshot [Daytona::Snapshot, String] Snapshot instance, ID, or name
194
+ # @yield [String] resolved Snapshot ID
195
+ # @yieldreturn [Object] operation result
196
+ # @return [Object] whatever the block returns
197
+ def call_with_resolved_id(snapshot)
198
+ return yield(snapshot.id) unless snapshot.is_a?(String)
199
+
200
+ if Snapshot.id?(snapshot)
201
+ begin
202
+ return yield(snapshot)
203
+ rescue DaytonaApiClient::ApiError => e
204
+ raise unless e.code == 404
205
+ end
206
+ end
207
+
208
+ resolved = snapshots_api.get_snapshot(snapshot)
209
+ yield(resolved.id)
210
+ end
211
+
177
212
  # Wait for snapshot to reach a terminal state (ACTIVE, ERROR, or BUILD_FAILED)
178
213
  # Optionally streams logs if on_logs callback is provided
179
214
  #
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.202.0
4
+ version: 0.204.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.202.0
88
+ version: 0.204.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.202.0
95
+ version: 0.204.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.202.0
102
+ version: 0.204.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.202.0
109
+ version: 0.204.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.202.0
116
+ version: 0.204.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.202.0
123
+ version: 0.204.0
124
124
  - !ruby/object:Gem::Dependency
125
125
  name: dotenv
126
126
  requirement: !ruby/object:Gem::Requirement
@@ -184,7 +184,7 @@ dependencies:
184
184
  - !ruby/object:Gem::Version
185
185
  version: '0.6'
186
186
  description: 'High-level Ruby SDK for Daytona: sandboxes, git, filesystem, LSP, process,
187
- and object storage.'
187
+ and object storage. Requires Ruby >= 3.2.'
188
188
  email:
189
189
  - support@daytona.io
190
190
  executables: []