octaspace 0.1.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 +7 -0
- data/CHANGELOG.md +22 -0
- data/MIT-LICENSE +22 -0
- data/README.md +336 -0
- data/lib/octaspace/client.rb +109 -0
- data/lib/octaspace/configuration.rb +136 -0
- data/lib/octaspace/errors.rb +76 -0
- data/lib/octaspace/middleware/url_rotator.rb +92 -0
- data/lib/octaspace/railtie.rb +28 -0
- data/lib/octaspace/resources/accounts.rb +33 -0
- data/lib/octaspace/resources/apps.rb +19 -0
- data/lib/octaspace/resources/base.rb +35 -0
- data/lib/octaspace/resources/idle_jobs.rb +32 -0
- data/lib/octaspace/resources/network.rb +18 -0
- data/lib/octaspace/resources/nodes.rb +63 -0
- data/lib/octaspace/resources/services/machine_rental.rb +48 -0
- data/lib/octaspace/resources/services/render.rb +30 -0
- data/lib/octaspace/resources/services/session_proxy.rb +49 -0
- data/lib/octaspace/resources/services/vpn.rb +30 -0
- data/lib/octaspace/resources/services.rb +39 -0
- data/lib/octaspace/resources/sessions.rb +23 -0
- data/lib/octaspace/response.rb +40 -0
- data/lib/octaspace/transport/base.rb +51 -0
- data/lib/octaspace/transport/faraday_transport.rb +160 -0
- data/lib/octaspace/transport/persistent_transport.rb +88 -0
- data/lib/octaspace/types/account.rb +11 -0
- data/lib/octaspace/types/balance.rb +11 -0
- data/lib/octaspace/types/node.rb +12 -0
- data/lib/octaspace/types/session.rb +11 -0
- data/lib/octaspace/version.rb +5 -0
- data/lib/octaspace.rb +94 -0
- metadata +103 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OctaSpace
|
|
4
|
+
# Base error class for all OctaSpace SDK errors
|
|
5
|
+
class Error < StandardError
|
|
6
|
+
attr_reader :response, :status, :request_id
|
|
7
|
+
|
|
8
|
+
def initialize(message = nil, response: nil)
|
|
9
|
+
@response = response
|
|
10
|
+
@status = response&.status
|
|
11
|
+
@request_id = response&.request_id
|
|
12
|
+
super(message || "OctaSpace API error")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Raised when SDK is misconfigured (e.g., missing required gems for persistent mode)
|
|
17
|
+
class ConfigurationError < Error; end
|
|
18
|
+
|
|
19
|
+
# Network-level errors (before HTTP response is received)
|
|
20
|
+
class NetworkError < Error; end
|
|
21
|
+
class ConnectionError < NetworkError; end
|
|
22
|
+
class TimeoutError < NetworkError; end
|
|
23
|
+
|
|
24
|
+
# API-level errors (HTTP response received, but indicates failure)
|
|
25
|
+
class ApiError < Error; end
|
|
26
|
+
|
|
27
|
+
# 401 Unauthorized
|
|
28
|
+
class AuthenticationError < ApiError; end
|
|
29
|
+
|
|
30
|
+
# 403 Forbidden
|
|
31
|
+
class PermissionError < ApiError; end
|
|
32
|
+
|
|
33
|
+
# 404 Not Found
|
|
34
|
+
class NotFoundError < ApiError; end
|
|
35
|
+
|
|
36
|
+
# 422 Unprocessable Entity
|
|
37
|
+
class ValidationError < ApiError; end
|
|
38
|
+
|
|
39
|
+
# 429 Too Many Requests — includes Retry-After header value
|
|
40
|
+
class RateLimitError < ApiError
|
|
41
|
+
attr_reader :retry_after
|
|
42
|
+
|
|
43
|
+
def initialize(message = nil, response: nil)
|
|
44
|
+
@retry_after = response&.retry_after
|
|
45
|
+
super
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# 5xx Server Errors
|
|
50
|
+
class ServerError < ApiError; end
|
|
51
|
+
class BadGatewayError < ServerError; end # 502
|
|
52
|
+
class ServiceUnavailableError < ServerError; end # 503
|
|
53
|
+
class GatewayTimeoutError < ServerError; end # 504
|
|
54
|
+
|
|
55
|
+
# HTTP status code → exception class mapping
|
|
56
|
+
STATUS_ERRORS = {
|
|
57
|
+
401 => AuthenticationError,
|
|
58
|
+
403 => PermissionError,
|
|
59
|
+
404 => NotFoundError,
|
|
60
|
+
422 => ValidationError,
|
|
61
|
+
429 => RateLimitError,
|
|
62
|
+
502 => BadGatewayError,
|
|
63
|
+
503 => ServiceUnavailableError,
|
|
64
|
+
504 => GatewayTimeoutError
|
|
65
|
+
}.freeze
|
|
66
|
+
|
|
67
|
+
# Build the appropriate error for a given Response
|
|
68
|
+
# @param response [OctaSpace::Response]
|
|
69
|
+
# @return [OctaSpace::ApiError] subclass instance
|
|
70
|
+
def self.error_for(response)
|
|
71
|
+
klass = STATUS_ERRORS.fetch(response.status) do
|
|
72
|
+
response.server_error? ? ServerError : ApiError
|
|
73
|
+
end
|
|
74
|
+
klass.new(response: response)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OctaSpace
|
|
4
|
+
module Middleware
|
|
5
|
+
# Thread-safe round-robin URL rotator with automatic failover
|
|
6
|
+
#
|
|
7
|
+
# When multiple base_urls are configured, requests are distributed
|
|
8
|
+
# across all healthy URLs. Failed URLs enter a cooldown period before
|
|
9
|
+
# being re-admitted to the rotation.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# rotator = OctaSpace::Middleware::UrlRotator.new([
|
|
13
|
+
# "https://api.octa.space",
|
|
14
|
+
# "https://api2.octa.space"
|
|
15
|
+
# ])
|
|
16
|
+
#
|
|
17
|
+
# url = rotator.next_url
|
|
18
|
+
# rotator.mark_failed(url)
|
|
19
|
+
# rotator.stats # => { total: 2, available: 1, failed: ["https://api.octa.space"] }
|
|
20
|
+
class UrlRotator
|
|
21
|
+
# Seconds a failed URL is excluded from rotation
|
|
22
|
+
FAILURE_COOLDOWN = 30
|
|
23
|
+
|
|
24
|
+
# @param urls [Array<String>] ordered list of API base URLs
|
|
25
|
+
def initialize(urls)
|
|
26
|
+
@urls = urls.dup.freeze
|
|
27
|
+
@counter = 0
|
|
28
|
+
@failed = {} # url => Time failed_at
|
|
29
|
+
@mutex = Mutex.new
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @return [String] next available URL (round-robin), falls back to first if all failed
|
|
33
|
+
def next_url
|
|
34
|
+
available = available_urls
|
|
35
|
+
return @urls.first if available.empty?
|
|
36
|
+
|
|
37
|
+
@mutex.synchronize do
|
|
38
|
+
idx = @counter % available.size
|
|
39
|
+
@counter += 1
|
|
40
|
+
available[idx]
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Mark a URL as temporarily failed
|
|
45
|
+
# @param url [String]
|
|
46
|
+
def mark_failed(url)
|
|
47
|
+
@mutex.synchronize { @failed[url] = Time.now }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Mark a URL as recovered (remove from failed list)
|
|
51
|
+
# @param url [String]
|
|
52
|
+
def mark_success(url)
|
|
53
|
+
@mutex.synchronize { @failed.delete(url) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @return [Array<String>] currently healthy URLs (excluding cooldown)
|
|
57
|
+
def available_urls
|
|
58
|
+
now = Time.now
|
|
59
|
+
@mutex.synchronize do
|
|
60
|
+
@urls.reject do |url|
|
|
61
|
+
failed_at = @failed[url]
|
|
62
|
+
next false unless failed_at
|
|
63
|
+
|
|
64
|
+
if now - failed_at < FAILURE_COOLDOWN
|
|
65
|
+
true
|
|
66
|
+
else
|
|
67
|
+
@failed.delete(url)
|
|
68
|
+
false
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @return [Hash] diagnostic stats
|
|
75
|
+
def stats
|
|
76
|
+
{
|
|
77
|
+
total: @urls.size,
|
|
78
|
+
available: available_urls.size,
|
|
79
|
+
failed: @mutex.synchronize { @failed.keys }
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Reset state — useful in tests
|
|
84
|
+
def reset!
|
|
85
|
+
@mutex.synchronize do
|
|
86
|
+
@counter = 0
|
|
87
|
+
@failed.clear
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OctaSpace
|
|
4
|
+
# Rails integration via Railtie
|
|
5
|
+
#
|
|
6
|
+
# Automatically loaded when Rails is present (see lib/octaspace.rb).
|
|
7
|
+
# Allows configuration via config/initializers/octaspace.rb:
|
|
8
|
+
#
|
|
9
|
+
# OctaSpace.configure do |config|
|
|
10
|
+
# config.api_key = ENV["OCTA_API_KEY"]
|
|
11
|
+
# config.keep_alive = true
|
|
12
|
+
# config.pool_size = ENV.fetch("RAILS_MAX_THREADS", 5).to_i
|
|
13
|
+
# config.logger = Rails.logger
|
|
14
|
+
# end
|
|
15
|
+
class Railtie < ::Rails::Railtie
|
|
16
|
+
initializer "octaspace.configure" do
|
|
17
|
+
# No-op: users call OctaSpace.configure {} directly in their initializer.
|
|
18
|
+
# This hook exists for future extensions (e.g., auto-configure from credentials).
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Gracefully shut down the shared client's persistent connection pools
|
|
22
|
+
# when the Rails process stops. Only applies when keep_alive: true is used
|
|
23
|
+
# and the shared client (OctaSpace.client) has been accessed.
|
|
24
|
+
config.after_initialize do
|
|
25
|
+
at_exit { OctaSpace.shutdown_shared_client! }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OctaSpace
|
|
4
|
+
module Resources
|
|
5
|
+
# Account-related API endpoints
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# client.accounts.profile
|
|
9
|
+
# client.accounts.balance
|
|
10
|
+
class Accounts < Base
|
|
11
|
+
# Fetch the authenticated user's profile
|
|
12
|
+
# GET /accounts
|
|
13
|
+
# @return [OctaSpace::Response]
|
|
14
|
+
def profile
|
|
15
|
+
get("/accounts")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Fetch the authenticated user's balance
|
|
19
|
+
# GET /accounts/balance
|
|
20
|
+
# @return [OctaSpace::Response]
|
|
21
|
+
def balance
|
|
22
|
+
get("/accounts/balance")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Generate / create a new wallet for the authenticated user
|
|
26
|
+
# POST /accounts
|
|
27
|
+
# @return [OctaSpace::Response]
|
|
28
|
+
def generate_wallet
|
|
29
|
+
post("/accounts")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OctaSpace
|
|
4
|
+
module Resources
|
|
5
|
+
# Apps API endpoint
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# client.apps.list
|
|
9
|
+
class Apps < Base
|
|
10
|
+
# List available apps
|
|
11
|
+
# GET /apps
|
|
12
|
+
# @param params [Hash] optional filter params
|
|
13
|
+
# @return [OctaSpace::Response]
|
|
14
|
+
def list(**params)
|
|
15
|
+
get("/apps", params:)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module OctaSpace
|
|
6
|
+
module Resources
|
|
7
|
+
# Base class for all API resource groups
|
|
8
|
+
#
|
|
9
|
+
# Delegates HTTP methods to the transport layer and provides
|
|
10
|
+
# a clean DSL for subclasses.
|
|
11
|
+
class Base
|
|
12
|
+
# @param transport [OctaSpace::Transport::Base]
|
|
13
|
+
def initialize(transport)
|
|
14
|
+
@transport = transport
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
attr_reader :transport
|
|
20
|
+
|
|
21
|
+
def get(path, **opts) = transport.get(path, **opts)
|
|
22
|
+
def post(path, **opts) = transport.post(path, **opts)
|
|
23
|
+
def put(path, **opts) = transport.put(path, **opts)
|
|
24
|
+
def patch(path, **opts) = transport.patch(path, **opts)
|
|
25
|
+
def delete(path, **opts) = transport.delete(path, **opts)
|
|
26
|
+
|
|
27
|
+
# Encode a single path segment to prevent path traversal
|
|
28
|
+
# @param segment [String, Integer]
|
|
29
|
+
# @return [String]
|
|
30
|
+
def encode(segment)
|
|
31
|
+
URI.encode_www_form_component(segment.to_s)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OctaSpace
|
|
4
|
+
module Resources
|
|
5
|
+
# Idle Jobs API endpoints
|
|
6
|
+
#
|
|
7
|
+
# Each idle job is identified by both a node ID and a job ID.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# client.idle_jobs.find(node_id: 69, job_id: 42)
|
|
11
|
+
# client.idle_jobs.logs(node_id: 69, job_id: 42)
|
|
12
|
+
class IdleJobs < Base
|
|
13
|
+
# Fetch a single idle job status
|
|
14
|
+
# GET /idle_jobs/:node_id/:job_id
|
|
15
|
+
# @param node_id [Integer, String]
|
|
16
|
+
# @param job_id [Integer, String]
|
|
17
|
+
# @return [OctaSpace::Response]
|
|
18
|
+
def find(node_id:, job_id:)
|
|
19
|
+
get("/idle_jobs/#{encode(node_id)}/#{encode(job_id)}")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Fetch idle job logs
|
|
23
|
+
# GET /idle_jobs/:node_id/:job_id/logs
|
|
24
|
+
# @param node_id [Integer, String]
|
|
25
|
+
# @param job_id [Integer, String]
|
|
26
|
+
# @return [OctaSpace::Response]
|
|
27
|
+
def logs(node_id:, job_id:)
|
|
28
|
+
get("/idle_jobs/#{encode(node_id)}/#{encode(job_id)}/logs")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OctaSpace
|
|
4
|
+
module Resources
|
|
5
|
+
# Network information endpoint
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# client.network.info
|
|
9
|
+
class Network < Base
|
|
10
|
+
# Fetch combined network information (blockchain, market, nodes, power, etc.)
|
|
11
|
+
# GET /network
|
|
12
|
+
# @return [OctaSpace::Response]
|
|
13
|
+
def info
|
|
14
|
+
get("/network")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OctaSpace
|
|
4
|
+
module Resources
|
|
5
|
+
# Node-related API endpoints
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# client.nodes.list
|
|
9
|
+
# client.nodes.find(123)
|
|
10
|
+
# client.nodes.reboot(123)
|
|
11
|
+
# client.nodes.update_prices(123, gpu_hour: 0.5, cpu_hour: 0.1)
|
|
12
|
+
class Nodes < Base
|
|
13
|
+
# List all nodes
|
|
14
|
+
# GET /nodes
|
|
15
|
+
# @param params [Hash] optional filter/pagination params
|
|
16
|
+
# @return [OctaSpace::Response]
|
|
17
|
+
def list(**params)
|
|
18
|
+
get("/nodes", params:)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Fetch a single node by ID
|
|
22
|
+
# GET /nodes/:id
|
|
23
|
+
# @param id [Integer, String]
|
|
24
|
+
# @return [OctaSpace::Response]
|
|
25
|
+
def find(id)
|
|
26
|
+
get("/nodes/#{encode(id)}")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Download node identity file (binary response)
|
|
30
|
+
# GET /nodes/:id/ident
|
|
31
|
+
# @param id [Integer, String]
|
|
32
|
+
# @return [OctaSpace::Response]
|
|
33
|
+
def download_ident(id)
|
|
34
|
+
get("/nodes/#{encode(id)}/ident")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Download node logs (binary response)
|
|
38
|
+
# GET /nodes/:id/logs
|
|
39
|
+
# @param id [Integer, String]
|
|
40
|
+
# @return [OctaSpace::Response]
|
|
41
|
+
def download_logs(id)
|
|
42
|
+
get("/nodes/#{encode(id)}/logs")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Update node pricing
|
|
46
|
+
# PATCH /nodes/:id/prices
|
|
47
|
+
# @param id [Integer, String]
|
|
48
|
+
# @param prices [Hash] e.g. { gpu_hour: 0.5, cpu_hour: 0.1 }
|
|
49
|
+
# @return [OctaSpace::Response]
|
|
50
|
+
def update_prices(id, **prices)
|
|
51
|
+
patch("/nodes/#{encode(id)}/prices", body: prices)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Reboot a node
|
|
55
|
+
# GET /nodes/:id/reboot
|
|
56
|
+
# @param id [Integer, String]
|
|
57
|
+
# @return [OctaSpace::Response]
|
|
58
|
+
def reboot(id)
|
|
59
|
+
get("/nodes/#{encode(id)}/reboot")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OctaSpace
|
|
4
|
+
module Resources
|
|
5
|
+
class Services
|
|
6
|
+
# Machine Rental (MR) service endpoints
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# client.services.mr.list
|
|
10
|
+
# client.services.mr.create(
|
|
11
|
+
# node_id: 123,
|
|
12
|
+
# disk_size: 10,
|
|
13
|
+
# image: "ubuntu:24.04",
|
|
14
|
+
# app: "249b4cb3-3db1-4c06-98a4-772ba88cd81c"
|
|
15
|
+
# )
|
|
16
|
+
class MachineRental < Base
|
|
17
|
+
# List available / active machine rentals
|
|
18
|
+
# GET /services/mr
|
|
19
|
+
# @param params [Hash] optional filter params
|
|
20
|
+
# @return [OctaSpace::Response]
|
|
21
|
+
def list(**params)
|
|
22
|
+
get("/services/mr", params:)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Create (start) a machine rental
|
|
26
|
+
# POST /services/mr
|
|
27
|
+
# @param attrs [Hash] rental parameters
|
|
28
|
+
# @return [OctaSpace::Response]
|
|
29
|
+
def create(**attrs)
|
|
30
|
+
item = {
|
|
31
|
+
id: 0,
|
|
32
|
+
node_id: attrs.fetch(:node_id),
|
|
33
|
+
disk_size: attrs.fetch(:disk_size),
|
|
34
|
+
image: attrs.fetch(:image),
|
|
35
|
+
app: attrs[:app].to_s,
|
|
36
|
+
envs: attrs[:envs] || {},
|
|
37
|
+
ports: attrs[:ports] || [],
|
|
38
|
+
http_ports: attrs[:http_ports] || [],
|
|
39
|
+
start_command: attrs[:start_command].to_s,
|
|
40
|
+
entrypoint: attrs[:entrypoint].to_s
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
post("/services/mr", body: [item])
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OctaSpace
|
|
4
|
+
module Resources
|
|
5
|
+
class Services
|
|
6
|
+
# Render service endpoints
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# client.services.render.list
|
|
10
|
+
# client.services.render.create(node_id: 123, disk_size: 100)
|
|
11
|
+
class Render < Base
|
|
12
|
+
# List render jobs
|
|
13
|
+
# GET /services/render
|
|
14
|
+
# @param params [Hash] optional filter params
|
|
15
|
+
# @return [OctaSpace::Response]
|
|
16
|
+
def list(**params)
|
|
17
|
+
get("/services/render", params:)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Create (start) a render job
|
|
21
|
+
# POST /services/render
|
|
22
|
+
# @param attrs [Hash] render job parameters
|
|
23
|
+
# @return [OctaSpace::Response]
|
|
24
|
+
def create(**attrs)
|
|
25
|
+
post("/services/render", body: attrs)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module OctaSpace
|
|
6
|
+
module Resources
|
|
7
|
+
class Services
|
|
8
|
+
# Proxy object for operations on a specific service session
|
|
9
|
+
#
|
|
10
|
+
# Obtained via: client.services.session("uuid")
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# session = client.services.session("abc-123")
|
|
14
|
+
# session.info
|
|
15
|
+
# session.logs
|
|
16
|
+
# session.stop(score: 5)
|
|
17
|
+
class SessionProxy
|
|
18
|
+
# @param transport [OctaSpace::Transport::Base]
|
|
19
|
+
# @param uuid [String] session UUID
|
|
20
|
+
def initialize(transport, uuid)
|
|
21
|
+
@transport = transport
|
|
22
|
+
@uuid = URI.encode_www_form_component(uuid.to_s)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Fetch session details
|
|
26
|
+
# GET /services/:uuid/info
|
|
27
|
+
# @return [OctaSpace::Response]
|
|
28
|
+
def info
|
|
29
|
+
@transport.get("/services/#{@uuid}/info")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Fetch session logs
|
|
33
|
+
# GET /services/:uuid/logs
|
|
34
|
+
# @return [OctaSpace::Response]
|
|
35
|
+
def logs
|
|
36
|
+
@transport.get("/services/#{@uuid}/logs")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Stop the session
|
|
40
|
+
# POST /services/:uuid/stop
|
|
41
|
+
# @param params [Hash] e.g. { score: 5 }
|
|
42
|
+
# @return [OctaSpace::Response]
|
|
43
|
+
def stop(**params)
|
|
44
|
+
@transport.post("/services/#{@uuid}/stop", body: params.empty? ? nil : params)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OctaSpace
|
|
4
|
+
module Resources
|
|
5
|
+
class Services
|
|
6
|
+
# VPN service endpoints
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# client.services.vpn.list
|
|
10
|
+
# client.services.vpn.create(node_id: 123)
|
|
11
|
+
class Vpn < Base
|
|
12
|
+
# List active VPN sessions
|
|
13
|
+
# GET /services/vpn
|
|
14
|
+
# @param params [Hash] optional filter params
|
|
15
|
+
# @return [OctaSpace::Response]
|
|
16
|
+
def list(**params)
|
|
17
|
+
get("/services/vpn", params:)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Create (start) a VPN session
|
|
21
|
+
# POST /services/vpn
|
|
22
|
+
# @param attrs [Hash] VPN parameters
|
|
23
|
+
# @return [OctaSpace::Response]
|
|
24
|
+
def create(**attrs)
|
|
25
|
+
post("/services/vpn", body: attrs)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OctaSpace
|
|
4
|
+
module Resources
|
|
5
|
+
# Services namespace — aggregates MR, VPN, Render subresources
|
|
6
|
+
# and provides the session proxy pattern
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# client.services.mr.list
|
|
10
|
+
# client.services.vpn.create(node_id: 123)
|
|
11
|
+
# client.services.render.create(node_id: 456, disk_size: 100)
|
|
12
|
+
#
|
|
13
|
+
# # Session proxy pattern
|
|
14
|
+
# client.services.session("uuid-123").info
|
|
15
|
+
# client.services.session("uuid-123").stop(score: 5)
|
|
16
|
+
class Services < Base
|
|
17
|
+
attr_reader :mr, :vpn, :render
|
|
18
|
+
|
|
19
|
+
def initialize(transport)
|
|
20
|
+
super
|
|
21
|
+
@mr = Services::MachineRental.new(transport)
|
|
22
|
+
@vpn = Services::Vpn.new(transport)
|
|
23
|
+
@render = Services::Render.new(transport)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Return a proxy object for operations on a specific session
|
|
27
|
+
# @param uuid [String] session UUID
|
|
28
|
+
# @return [OctaSpace::Resources::Services::SessionProxy]
|
|
29
|
+
def session(uuid)
|
|
30
|
+
Services::SessionProxy.new(transport, uuid)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
require_relative "services/session_proxy"
|
|
37
|
+
require_relative "services/machine_rental"
|
|
38
|
+
require_relative "services/vpn"
|
|
39
|
+
require_relative "services/render"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OctaSpace
|
|
4
|
+
module Resources
|
|
5
|
+
# Session listing endpoint
|
|
6
|
+
#
|
|
7
|
+
# For operations on a specific session (info/logs/stop),
|
|
8
|
+
# use the proxy pattern: client.services.session("uuid")
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# client.sessions.list
|
|
12
|
+
# client.sessions.list(recent: true)
|
|
13
|
+
class Sessions < Base
|
|
14
|
+
# List all sessions
|
|
15
|
+
# GET /sessions
|
|
16
|
+
# @param params [Hash] optional filter params
|
|
17
|
+
# @return [OctaSpace::Response]
|
|
18
|
+
def list(**params)
|
|
19
|
+
get("/sessions", params:)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OctaSpace
|
|
4
|
+
# Wraps a raw Faraday response, providing a stable SDK interface
|
|
5
|
+
class Response
|
|
6
|
+
attr_reader :status, :headers, :body, :data
|
|
7
|
+
|
|
8
|
+
# @param faraday_response [Faraday::Response]
|
|
9
|
+
def initialize(faraday_response)
|
|
10
|
+
@status = faraday_response.status
|
|
11
|
+
@headers = faraday_response.headers
|
|
12
|
+
@body = faraday_response.body
|
|
13
|
+
# Faraday :json middleware parses JSON body into a Hash/Array automatically
|
|
14
|
+
@data = faraday_response.body
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @return [Boolean] true for 2xx status codes
|
|
18
|
+
def success? = (200..299).cover?(status)
|
|
19
|
+
|
|
20
|
+
# @return [Boolean] true for 4xx status codes
|
|
21
|
+
def client_error? = (400..499).cover?(status)
|
|
22
|
+
|
|
23
|
+
# @return [Boolean] true for 5xx status codes
|
|
24
|
+
def server_error? = (500..599).cover?(status)
|
|
25
|
+
|
|
26
|
+
# @return [Boolean] true for any non-2xx error
|
|
27
|
+
def error? = client_error? || server_error?
|
|
28
|
+
|
|
29
|
+
# @return [String, nil] X-Request-Id header value
|
|
30
|
+
def request_id = headers["x-request-id"]
|
|
31
|
+
|
|
32
|
+
# @return [Integer, nil] Retry-After header value in seconds
|
|
33
|
+
def retry_after = headers["retry-after"]&.to_i
|
|
34
|
+
|
|
35
|
+
def to_s
|
|
36
|
+
"#<OctaSpace::Response status=#{status}>"
|
|
37
|
+
end
|
|
38
|
+
alias_method :inspect, :to_s
|
|
39
|
+
end
|
|
40
|
+
end
|