octaspace 0.1.0 → 0.2.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: 9dd2b5c9ae96cbc0bef9e36fd492b2a1dbdcf53af596a82193986039ff8a4da9
4
- data.tar.gz: b0f0a7a1a3301be4f3d1dbe33f60652fc5c9e4b36e709779076d374fb0e16ff4
3
+ metadata.gz: 1826290acf7155b153139f322c170ad14e369808d22f1333fa08571dd8ee3d60
4
+ data.tar.gz: cb3beab9b8e3a29f01cde3049f19cbd20e37103b32b62e52bde63482ef4a0a3b
5
5
  SHA512:
6
- metadata.gz: 59cb1140ec7e34939b2535857d4d7afd395b8b25d45ac69df2c14c524bd1e14503f969704299fe6aa3d51021fa2491a8f00d0cf4dee3a3d848308a9aa9fb577d
7
- data.tar.gz: 6b4353b1524bae1e594ef4109a93a371e539d48b157dcdd4e165a1283c60e4a86e719818f62276d3cac2628e4bf9cd419b651f5a4633302d391e3d5fc41f476d
6
+ metadata.gz: 2f158f5d579814e26c153776b24dfb29b273a2ff583dc64c2d806dd91616e8a7141465620c38f010a4efccacff50f07452d072af96e3135590d1a1c6a6654549
7
+ data.tar.gz: 588659f9b8449eaed8565bd61d01190be13c5223009e5cfe5aafa6ea949d0834a5ae264c5d2b191f0cad1d9e7b051e653c1721f54ff2ec411d124a1742c9a547
data/CHANGELOG.md CHANGED
@@ -5,6 +5,33 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.0] - 2026-04-16
9
+
10
+ ### Added
11
+
12
+ - Support for `client.services.session(uuid).logs(recent: true)` for finished-session log retrieval.
13
+ - `OctaSpace::ProvisionRejectedError` for MR create requests that are transport-successful but rejected by the API payload contract.
14
+ - `OctaSpace::PayloadHelpers` with targeted helpers for stringified app port lists and marketplace bandwidth normalization.
15
+ - Diagnostics preset and manual runner support for recent session logs.
16
+ - Live-shaped fixtures for recent sessions and render marketplace responses.
17
+
18
+ ### Changed
19
+
20
+ - `services.mr.list`, `services.render.list`, and `services.vpn.list` are now documented and tested as marketplace catalog endpoints rather than session collections.
21
+ - `services.mr.create` now forwards optional `organization_id` and `project_id`.
22
+ - Playground Services now presents marketplace-oriented summaries with normalized bandwidth display.
23
+ - Playground and README copy now reflect live API quirks for app ports, recent sessions, and finished-session logs.
24
+ - Publishing guidance now uses an explicit manual release flow, and the local `rake release` helper is disabled to avoid accidental RubyGems publication.
25
+
26
+ ### Fixed
27
+
28
+ - Corrected the Ruby public API gap for recent finished-session logs.
29
+ - Corrected MR create handling for `HTTP 200` batch-style rejection responses.
30
+ - Corrected fixtures and tests that modeled `/services/mr` and `/services/vpn` as session lists.
31
+ - Corrected logs fixtures and tests to match the live `{system, container}` payload shape.
32
+ - Corrected the playground Apps page so port counts work when the live API returns stringified JSON arrays.
33
+ - Corrected test coverage for `sessions?recent=true` by using live-shaped string telemetry fields.
34
+
8
35
  ## [0.1.0] - 2026-04-12
9
36
 
10
37
  ### Added
@@ -19,4 +46,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19
46
  - Rails integration with automatic client sharing and graceful shutdown.
20
47
  - Interactive Playground app for manual testing and diagnostics.
21
48
 
22
- [0.1.0]: https://github.com/octaspace/ruby-sdk/compare/v0.1.0...HEAD
49
+ [0.2.0]: https://github.com/octaspace/ruby-sdk/compare/v0.1.0...HEAD
50
+ [0.1.0]: https://github.com/octaspace/ruby-sdk/releases/tag/v0.1.0
data/README.md CHANGED
@@ -62,17 +62,18 @@ client.sessions.list # GET /sessions
62
62
  session = client.services.session("uuid-here")
63
63
  session.info # GET /services/:uuid/info
64
64
  session.logs # GET /services/:uuid/logs
65
+ session.logs(recent: true) # GET /services/:uuid/logs?recent=true for finished sessions
65
66
  session.stop(score: 5) # POST /services/:uuid/stop
66
67
 
67
68
  # Services
68
- client.services.mr.list # GET /services/mr
69
+ client.services.mr.list # GET /services/mr (marketplace machine catalog)
69
70
  client.services.mr.create(
70
71
  node_id: 1,
71
72
  disk_size: 10,
72
73
  image: "ubuntu:24.04",
73
74
  app: "249b4cb3-3db1-4c06-98a4-772ba88cd81c"
74
75
  ) # POST /services/mr
75
- client.services.vpn.list # GET /services/vpn
76
+ client.services.vpn.list # GET /services/vpn (VPN relay catalog)
76
77
  client.services.vpn.create(node_id: 1, subkind: "wg") # POST /services/vpn
77
78
  client.services.render.list # GET /services/render
78
79
  client.services.render.create(node_id: 1, disk_size: 100) # POST /services/render
@@ -80,6 +81,9 @@ client.services.render.create(node_id: 1, disk_size: 100) # POST /services/rende
80
81
  # Apps
81
82
  client.apps.list # GET /apps
82
83
 
84
+ # Note: live API may serialize app port lists as JSON strings
85
+ # (for example "[]"). The raw response is preserved by the SDK.
86
+
83
87
  # Network
84
88
  client.network.info # GET /network
85
89
 
@@ -269,9 +273,9 @@ Pages:
269
273
  |---|---|
270
274
  | `/playground/account` | Profile + balance |
271
275
  | `/playground/nodes` | Node list with state badges |
272
- | `/playground/sessions` | Active sessions |
273
- | `/playground/services` | Machine Rentals + VPN sessions |
274
- | `/playground/diagnostics` | Transport mode, pool stats, URL rotator state (auto-refresh every 5s) |
276
+ | `/playground/sessions` | Current + recent sessions, including live-format quirks |
277
+ | `/playground/services` | Marketplace catalogs for MR, Render, and VPN |
278
+ | `/playground/diagnostics` | Direct SDK method runner for contracts, payloads, transport mode, and pool stats |
275
279
 
276
280
  ## Development
277
281
 
@@ -288,7 +292,7 @@ bundle exec rake test # tests only
288
292
  You can verify the gem in a clean Ruby environment without Rails:
289
293
 
290
294
  1. Build the gem: `gem build octaspace.gemspec`
291
- 2. Install it locally: `gem install ./octaspace-0.1.0.gem`
295
+ 2. Install it locally: `gem install ./octaspace-0.2.0.gem`
292
296
  3. Test in IRB:
293
297
 
294
298
  ```ruby
@@ -310,7 +314,7 @@ bundle exec appraisal rails-8-0 rake test
310
314
 
311
315
  ### Dummy Application (Playground)
312
316
 
313
- The repository includes a Rails "Dummy" application for manual testing and UI prototyping. It is located in `test/dummy`.`.
317
+ The repository includes a Rails "Dummy" application for manual testing and UI prototyping. It is located in `test/dummy`.
314
318
 
315
319
  To run the dummy app:
316
320
 
@@ -24,6 +24,32 @@ module OctaSpace
24
24
  # API-level errors (HTTP response received, but indicates failure)
25
25
  class ApiError < Error; end
26
26
 
27
+ # Domain-level rejection where transport succeeded but the provision request
28
+ # was rejected by the API payload contract.
29
+ class ProvisionRejectedError < Error
30
+ attr_reader :rejections
31
+
32
+ def initialize(message = nil, response: nil, rejections: [])
33
+ @rejections = Array(rejections)
34
+ super(message || build_message(@rejections), response: response)
35
+ end
36
+
37
+ private
38
+
39
+ def build_message(rejections)
40
+ first_reason =
41
+ rejections.filter_map do |item|
42
+ next unless item.is_a?(Hash)
43
+
44
+ item["reason"] || item[:reason]
45
+ end.first
46
+
47
+ return "Provision request rejected" if first_reason.to_s.empty?
48
+
49
+ "Provision request rejected: #{first_reason}"
50
+ end
51
+ end
52
+
27
53
  # 401 Unauthorized
28
54
  class AuthenticationError < ApiError; end
29
55
 
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module OctaSpace
6
+ module PayloadHelpers
7
+ module_function
8
+
9
+ def parse_port_list(value)
10
+ case value
11
+ when nil
12
+ []
13
+ when Array
14
+ value
15
+ when String
16
+ parse_stringified_port_list(value)
17
+ else
18
+ Array(value)
19
+ end
20
+ end
21
+
22
+ def normalize_marketplace_bandwidth(value)
23
+ numeric =
24
+ case value
25
+ when nil
26
+ return nil
27
+ when Numeric
28
+ value.to_f
29
+ when String
30
+ Float(value)
31
+ else
32
+ return value
33
+ end
34
+
35
+ return numeric unless numeric > 100_000
36
+
37
+ numeric / 125_000.0
38
+ rescue ArgumentError, TypeError
39
+ value
40
+ end
41
+
42
+ def parse_stringified_port_list(value)
43
+ stripped = value.strip
44
+ return [] if stripped.empty?
45
+
46
+ parsed = JSON.parse(stripped)
47
+ parsed.is_a?(Array) ? parsed : []
48
+ rescue JSON::ParserError
49
+ []
50
+ end
51
+ private_class_method :parse_stringified_port_list
52
+ end
53
+ end
@@ -14,7 +14,7 @@ module OctaSpace
14
14
  # app: "249b4cb3-3db1-4c06-98a4-772ba88cd81c"
15
15
  # )
16
16
  class MachineRental < Base
17
- # List available / active machine rentals
17
+ # List available marketplace machines for rent
18
18
  # GET /services/mr
19
19
  # @param params [Hash] optional filter params
20
20
  # @return [OctaSpace::Response]
@@ -40,7 +40,37 @@ module OctaSpace
40
40
  entrypoint: attrs[:entrypoint].to_s
41
41
  }
42
42
 
43
- post("/services/mr", body: [item])
43
+ item[:organization_id] = attrs[:organization_id] if attrs.key?(:organization_id)
44
+ item[:project_id] = attrs[:project_id] if attrs.key?(:project_id)
45
+
46
+ response = post("/services/mr", body: [item])
47
+ raise_if_rejected!(response)
48
+ response
49
+ end
50
+
51
+ private
52
+
53
+ def raise_if_rejected!(response)
54
+ rejections = extract_rejections(response.data)
55
+ return if rejections.empty?
56
+
57
+ raise OctaSpace::ProvisionRejectedError.new(response: response, rejections: rejections)
58
+ end
59
+
60
+ def extract_rejections(data)
61
+ return [] unless data.is_a?(Array)
62
+
63
+ data.filter_map do |item|
64
+ next unless item.is_a?(Hash)
65
+
66
+ reason = item["reason"] || item[:reason]
67
+ status = item["status"] || item[:status]
68
+ uuid = item["uuid"] || item[:uuid]
69
+ next if uuid
70
+ next unless reason || status.to_i.positive?
71
+
72
+ item
73
+ end
44
74
  end
45
75
  end
46
76
  end
@@ -31,9 +31,14 @@ module OctaSpace
31
31
 
32
32
  # Fetch session logs
33
33
  # GET /services/:uuid/logs
34
+ # @param recent [Boolean, nil] use the recent logs branch for finished sessions
34
35
  # @return [OctaSpace::Response]
35
- def logs
36
- @transport.get("/services/#{@uuid}/logs")
36
+ def logs(recent: nil)
37
+ params = {}
38
+ params[:recent] = true if recent
39
+ return @transport.get("/services/#{@uuid}/logs") if params.empty?
40
+
41
+ @transport.get("/services/#{@uuid}/logs", params:)
37
42
  end
38
43
 
39
44
  # Stop the session
@@ -9,7 +9,7 @@ module OctaSpace
9
9
  # client.services.vpn.list
10
10
  # client.services.vpn.create(node_id: 123)
11
11
  class Vpn < Base
12
- # List active VPN sessions
12
+ # List available VPN relay nodes
13
13
  # GET /services/vpn
14
14
  # @param params [Hash] optional filter params
15
15
  # @return [OctaSpace::Response]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OctaSpace
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/octaspace.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "octaspace/version"
4
4
  require "octaspace/errors"
5
+ require "octaspace/payload_helpers"
5
6
  require "octaspace/response"
6
7
  require "octaspace/configuration"
7
8
  require "octaspace/middleware/url_rotator"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: octaspace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OctaSpace Team
@@ -53,6 +53,7 @@ files:
53
53
  - lib/octaspace/configuration.rb
54
54
  - lib/octaspace/errors.rb
55
55
  - lib/octaspace/middleware/url_rotator.rb
56
+ - lib/octaspace/payload_helpers.rb
56
57
  - lib/octaspace/railtie.rb
57
58
  - lib/octaspace/resources/accounts.rb
58
59
  - lib/octaspace/resources/apps.rb