nvoi 0.1.5 → 0.1.6
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/.claude/todo/refactor/00-overview.md +171 -0
- data/.claude/todo/refactor/01-objects.md +96 -0
- data/.claude/todo/refactor/02-utils.md +143 -0
- data/.claude/todo/refactor/03-external-cloud.md +164 -0
- data/.claude/todo/refactor/04-external-dns.md +104 -0
- data/.claude/todo/refactor/05-external.md +133 -0
- data/.claude/todo/refactor/06-cli.md +123 -0
- data/.claude/todo/refactor/07-cli-deploy-command.md +177 -0
- data/.claude/todo/refactor/08-cli-deploy-steps.md +201 -0
- data/.claude/todo/refactor/09-cli-delete-command.md +169 -0
- data/.claude/todo/refactor/10-cli-exec-command.md +157 -0
- data/.claude/todo/refactor/11-cli-credentials-command.md +190 -0
- data/.claude/todo/refactor/_target.md +79 -0
- data/.claude/todo/scaleway.impl.md +644 -0
- data/.claude/todo/scaleway.reference.md +520 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +12 -2
- data/doc/config-schema.yaml +44 -11
- data/examples/golang/deploy.enc +0 -0
- data/examples/golang/main.go +18 -0
- data/exe/nvoi +3 -1
- data/lib/nvoi/cli/credentials/edit/command.rb +384 -0
- data/lib/nvoi/cli/credentials/show/command.rb +35 -0
- data/lib/nvoi/cli/db/command.rb +308 -0
- data/lib/nvoi/cli/delete/command.rb +75 -0
- data/lib/nvoi/cli/delete/steps/detach_volumes.rb +98 -0
- data/lib/nvoi/cli/delete/steps/teardown_dns.rb +49 -0
- data/lib/nvoi/cli/delete/steps/teardown_firewall.rb +46 -0
- data/lib/nvoi/cli/delete/steps/teardown_network.rb +30 -0
- data/lib/nvoi/cli/delete/steps/teardown_server.rb +50 -0
- data/lib/nvoi/cli/delete/steps/teardown_tunnel.rb +44 -0
- data/lib/nvoi/cli/delete/steps/teardown_volume.rb +61 -0
- data/lib/nvoi/cli/deploy/command.rb +184 -0
- data/lib/nvoi/cli/deploy/steps/build_image.rb +27 -0
- data/lib/nvoi/cli/deploy/steps/cleanup_images.rb +42 -0
- data/lib/nvoi/cli/deploy/steps/configure_tunnel.rb +100 -0
- data/lib/nvoi/cli/deploy/steps/deploy_service.rb +396 -0
- data/lib/nvoi/cli/deploy/steps/provision_network.rb +44 -0
- data/lib/nvoi/cli/deploy/steps/provision_server.rb +143 -0
- data/lib/nvoi/cli/deploy/steps/provision_volume.rb +171 -0
- data/lib/nvoi/cli/deploy/steps/setup_k3s.rb +481 -0
- data/lib/nvoi/cli/exec/command.rb +173 -0
- data/lib/nvoi/cli.rb +83 -142
- data/lib/nvoi/config_api/actions/app.rb +53 -0
- data/lib/nvoi/config_api/actions/compute_provider.rb +55 -0
- data/lib/nvoi/config_api/actions/database.rb +70 -0
- data/lib/nvoi/config_api/actions/env.rb +32 -0
- data/lib/nvoi/config_api/actions/secret.rb +32 -0
- data/lib/nvoi/config_api/actions/server.rb +66 -0
- data/lib/nvoi/config_api/actions/volume.rb +40 -0
- data/lib/nvoi/config_api/base.rb +44 -0
- data/lib/nvoi/config_api/result.rb +26 -0
- data/lib/nvoi/config_api.rb +70 -0
- data/lib/nvoi/errors.rb +68 -50
- data/lib/nvoi/external/cloud/aws.rb +425 -0
- data/lib/nvoi/external/cloud/base.rb +99 -0
- data/lib/nvoi/external/cloud/factory.rb +48 -0
- data/lib/nvoi/external/cloud/hetzner.rb +376 -0
- data/lib/nvoi/external/cloud/scaleway.rb +533 -0
- data/lib/nvoi/external/cloud.rb +15 -0
- data/lib/nvoi/external/containerd.rb +82 -0
- data/lib/nvoi/external/database/mysql.rb +84 -0
- data/lib/nvoi/external/database/postgres.rb +82 -0
- data/lib/nvoi/external/database/provider.rb +65 -0
- data/lib/nvoi/external/database/sqlite.rb +72 -0
- data/lib/nvoi/external/database.rb +22 -0
- data/lib/nvoi/external/dns/cloudflare.rb +292 -0
- data/lib/nvoi/external/kubectl.rb +65 -0
- data/lib/nvoi/external/ssh.rb +106 -0
- data/lib/nvoi/objects/config_override.rb +60 -0
- data/lib/nvoi/objects/configuration.rb +463 -0
- data/lib/nvoi/objects/database.rb +56 -0
- data/lib/nvoi/objects/dns.rb +14 -0
- data/lib/nvoi/objects/firewall.rb +11 -0
- data/lib/nvoi/objects/network.rb +11 -0
- data/lib/nvoi/objects/server.rb +14 -0
- data/lib/nvoi/objects/service_spec.rb +26 -0
- data/lib/nvoi/objects/tunnel.rb +14 -0
- data/lib/nvoi/objects/volume.rb +17 -0
- data/lib/nvoi/utils/config_loader.rb +172 -0
- data/lib/nvoi/utils/constants.rb +61 -0
- data/lib/nvoi/{credentials/manager.rb → utils/credential_store.rb} +16 -16
- data/lib/nvoi/{credentials → utils}/crypto.rb +8 -5
- data/lib/nvoi/{config → utils}/env_resolver.rb +10 -2
- data/lib/nvoi/utils/logger.rb +84 -0
- data/lib/nvoi/{config/naming.rb → utils/namer.rb} +28 -25
- data/lib/nvoi/{deployer → utils}/retry.rb +23 -3
- data/lib/nvoi/utils/templates.rb +62 -0
- data/lib/nvoi/version.rb +1 -1
- data/lib/nvoi.rb +10 -54
- data/templates/error-backend.yaml.erb +134 -0
- metadata +97 -44
- data/examples/golang/deploy.yml +0 -54
- data/lib/nvoi/cloudflare/client.rb +0 -287
- data/lib/nvoi/config/config.rb +0 -248
- data/lib/nvoi/config/loader.rb +0 -102
- data/lib/nvoi/config/ssh_keys.rb +0 -82
- data/lib/nvoi/config/types.rb +0 -274
- data/lib/nvoi/constants.rb +0 -59
- data/lib/nvoi/credentials/editor.rb +0 -272
- data/lib/nvoi/deployer/cleaner.rb +0 -36
- data/lib/nvoi/deployer/image_builder.rb +0 -23
- data/lib/nvoi/deployer/infrastructure.rb +0 -126
- data/lib/nvoi/deployer/orchestrator.rb +0 -146
- data/lib/nvoi/deployer/service_deployer.rb +0 -311
- data/lib/nvoi/deployer/tunnel_manager.rb +0 -57
- data/lib/nvoi/deployer/types.rb +0 -8
- data/lib/nvoi/k8s/renderer.rb +0 -44
- data/lib/nvoi/k8s/templates.rb +0 -29
- data/lib/nvoi/logger.rb +0 -72
- data/lib/nvoi/providers/aws.rb +0 -403
- data/lib/nvoi/providers/base.rb +0 -111
- data/lib/nvoi/providers/hetzner.rb +0 -288
- data/lib/nvoi/providers/hetzner_client.rb +0 -170
- data/lib/nvoi/remote/docker_manager.rb +0 -203
- data/lib/nvoi/remote/ssh_executor.rb +0 -72
- data/lib/nvoi/remote/volume_manager.rb +0 -103
- data/lib/nvoi/service/delete.rb +0 -234
- data/lib/nvoi/service/deploy.rb +0 -80
- data/lib/nvoi/service/exec.rb +0 -144
- data/lib/nvoi/service/provider.rb +0 -36
- data/lib/nvoi/steps/application_deployer.rb +0 -26
- data/lib/nvoi/steps/database_provisioner.rb +0 -60
- data/lib/nvoi/steps/k3s_cluster_setup.rb +0 -105
- data/lib/nvoi/steps/k3s_provisioner.rb +0 -351
- data/lib/nvoi/steps/server_provisioner.rb +0 -43
- data/lib/nvoi/steps/services_provisioner.rb +0 -29
- data/lib/nvoi/steps/tunnel_configurator.rb +0 -66
- data/lib/nvoi/steps/volume_provisioner.rb +0 -154
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Nvoi
|
|
7
|
+
module External
|
|
8
|
+
module Cloud
|
|
9
|
+
# Hetzner provider implements the compute provider interface for Hetzner Cloud
|
|
10
|
+
class Hetzner < Base
|
|
11
|
+
BASE_URL = "https://api.hetzner.cloud/v1"
|
|
12
|
+
|
|
13
|
+
def initialize(token)
|
|
14
|
+
@token = token
|
|
15
|
+
@conn = Faraday.new do |f|
|
|
16
|
+
f.request :json
|
|
17
|
+
f.response :json
|
|
18
|
+
f.headers["Authorization"] = "Bearer #{token}"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Network operations
|
|
23
|
+
|
|
24
|
+
def find_or_create_network(name)
|
|
25
|
+
network = find_network_by_name(name)
|
|
26
|
+
return to_network(network) if network
|
|
27
|
+
|
|
28
|
+
network = create_network_api(
|
|
29
|
+
name:,
|
|
30
|
+
ip_range: Utils::Constants::NETWORK_CIDR,
|
|
31
|
+
subnets: [{
|
|
32
|
+
type: "cloud",
|
|
33
|
+
ip_range: Utils::Constants::SUBNET_CIDR,
|
|
34
|
+
network_zone: "eu-central"
|
|
35
|
+
}]
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
to_network(network)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def get_network_by_name(name)
|
|
42
|
+
network = find_network_by_name(name)
|
|
43
|
+
raise Errors::NetworkError, "network not found: #{name}" unless network
|
|
44
|
+
|
|
45
|
+
to_network(network)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def delete_network(id)
|
|
49
|
+
delete("/networks/#{id.to_i}")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Firewall operations
|
|
53
|
+
|
|
54
|
+
def find_or_create_firewall(name)
|
|
55
|
+
firewall = find_firewall_by_name(name)
|
|
56
|
+
return to_firewall(firewall) if firewall
|
|
57
|
+
|
|
58
|
+
firewall = create_firewall_api(
|
|
59
|
+
name:,
|
|
60
|
+
rules: [{
|
|
61
|
+
direction: "in",
|
|
62
|
+
protocol: "tcp",
|
|
63
|
+
port: "22",
|
|
64
|
+
source_ips: ["0.0.0.0/0", "::/0"]
|
|
65
|
+
}]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
to_firewall(firewall)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def get_firewall_by_name(name)
|
|
72
|
+
firewall = find_firewall_by_name(name)
|
|
73
|
+
raise Errors::FirewallError, "firewall not found: #{name}" unless firewall
|
|
74
|
+
|
|
75
|
+
to_firewall(firewall)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def delete_firewall(id)
|
|
79
|
+
delete("/firewalls/#{id.to_i}")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Server operations
|
|
83
|
+
|
|
84
|
+
def find_server(name)
|
|
85
|
+
server = find_server_by_name(name)
|
|
86
|
+
return nil unless server
|
|
87
|
+
|
|
88
|
+
to_server(server)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def find_server_by_id(id)
|
|
92
|
+
server = get("/servers/#{id.to_i}")["server"]
|
|
93
|
+
return nil unless server
|
|
94
|
+
|
|
95
|
+
to_server(server)
|
|
96
|
+
rescue Errors::NotFoundError
|
|
97
|
+
nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def list_servers
|
|
101
|
+
get("/servers")["servers"].map { |s| to_server(s) }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def create_server(opts)
|
|
105
|
+
# Resolve IDs
|
|
106
|
+
server_type = find_server_type(opts.type)
|
|
107
|
+
raise Errors::ValidationError, "invalid server type: #{opts.type}" unless server_type
|
|
108
|
+
|
|
109
|
+
image = find_image(opts.image)
|
|
110
|
+
raise Errors::ValidationError, "invalid image: #{opts.image}" unless image
|
|
111
|
+
|
|
112
|
+
location = find_location(opts.location)
|
|
113
|
+
raise Errors::ValidationError, "invalid location: #{opts.location}" unless location
|
|
114
|
+
|
|
115
|
+
create_opts = {
|
|
116
|
+
name: opts.name,
|
|
117
|
+
server_type: server_type["name"],
|
|
118
|
+
image: image["name"],
|
|
119
|
+
location: location["name"],
|
|
120
|
+
user_data: opts.user_data,
|
|
121
|
+
start_after_create: true
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# Add network if provided
|
|
125
|
+
if opts.network_id && !opts.network_id.empty?
|
|
126
|
+
create_opts[:networks] = [opts.network_id.to_i]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Add firewall if provided
|
|
130
|
+
if opts.firewall_id && !opts.firewall_id.empty?
|
|
131
|
+
create_opts[:firewalls] = [{ firewall: opts.firewall_id.to_i }]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
server = post("/servers", create_opts)["server"]
|
|
135
|
+
to_server(server)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def wait_for_server(server_id, max_attempts)
|
|
139
|
+
server = Utils::Retry.poll(max_attempts: max_attempts, interval: Utils::Constants::SERVER_READY_INTERVAL) do
|
|
140
|
+
s = get("/servers/#{server_id.to_i}")["server"]
|
|
141
|
+
to_server(s) if s["status"] == "running"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
raise Errors::ServerCreationError, "server did not become running after #{max_attempts} attempts" unless server
|
|
145
|
+
|
|
146
|
+
server
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def delete_server(id)
|
|
150
|
+
server = get("/servers/#{id.to_i}")["server"]
|
|
151
|
+
|
|
152
|
+
# Remove from firewalls
|
|
153
|
+
get("/firewalls")["firewalls"].each do |fw|
|
|
154
|
+
fw["applied_to"]&.each do |applied|
|
|
155
|
+
next unless applied["type"] == "server" && applied.dig("server", "id") == id.to_i
|
|
156
|
+
|
|
157
|
+
remove_firewall_from_server(fw["id"], id.to_i)
|
|
158
|
+
rescue StandardError
|
|
159
|
+
# Ignore cleanup errors
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Detach from networks
|
|
164
|
+
server["private_net"]&.each do |pn|
|
|
165
|
+
detach_server_from_network(id.to_i, pn["network"])
|
|
166
|
+
rescue StandardError
|
|
167
|
+
# Ignore cleanup errors
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
delete("/servers/#{id.to_i}")
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Volume operations
|
|
174
|
+
|
|
175
|
+
def create_volume(opts)
|
|
176
|
+
server = get("/servers/#{opts.server_id.to_i}")["server"]
|
|
177
|
+
raise Errors::VolumeError, "server not found: #{opts.server_id}" unless server
|
|
178
|
+
|
|
179
|
+
volume = post("/volumes", {
|
|
180
|
+
name: opts.name,
|
|
181
|
+
size: opts.size,
|
|
182
|
+
location: server.dig("datacenter", "location", "name"),
|
|
183
|
+
format: "xfs"
|
|
184
|
+
})["volume"]
|
|
185
|
+
|
|
186
|
+
to_volume(volume)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def get_volume(id)
|
|
190
|
+
volume = get("/volumes/#{id.to_i}")["volume"]
|
|
191
|
+
return nil unless volume
|
|
192
|
+
|
|
193
|
+
to_volume(volume)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def get_volume_by_name(name)
|
|
197
|
+
volume = get("/volumes")["volumes"].find { |v| v["name"] == name }
|
|
198
|
+
return nil unless volume
|
|
199
|
+
|
|
200
|
+
to_volume(volume)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def delete_volume(id)
|
|
204
|
+
delete("/volumes/#{id.to_i}")
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def attach_volume(volume_id, server_id)
|
|
208
|
+
post("/volumes/#{volume_id.to_i}/actions/attach", { server: server_id.to_i })
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def detach_volume(volume_id)
|
|
212
|
+
post("/volumes/#{volume_id.to_i}/actions/detach", {})
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def wait_for_device_path(volume_id, _ssh)
|
|
216
|
+
# Hetzner provides device_path in API response
|
|
217
|
+
Utils::Retry.poll(max_attempts: 30, interval: 2) do
|
|
218
|
+
volume = get("/volumes/#{volume_id.to_i}")["volume"]
|
|
219
|
+
volume["linux_device"] if volume && volume["linux_device"] && !volume["linux_device"].empty?
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Validation operations
|
|
224
|
+
|
|
225
|
+
def validate_instance_type(instance_type)
|
|
226
|
+
server_type = find_server_type(instance_type)
|
|
227
|
+
raise Errors::ValidationError, "invalid hetzner server type: #{instance_type}" unless server_type
|
|
228
|
+
|
|
229
|
+
true
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def validate_region(region)
|
|
233
|
+
location = find_location(region)
|
|
234
|
+
raise Errors::ValidationError, "invalid hetzner location: #{region}" unless location
|
|
235
|
+
|
|
236
|
+
true
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def validate_credentials
|
|
240
|
+
get("/server_types")
|
|
241
|
+
true
|
|
242
|
+
rescue Errors::AuthenticationError => e
|
|
243
|
+
raise Errors::ValidationError, "hetzner credentials invalid: #{e.message}"
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
private
|
|
247
|
+
|
|
248
|
+
def get(path)
|
|
249
|
+
response = @conn.get("#{BASE_URL}#{path}")
|
|
250
|
+
handle_response(response)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def post(path, payload = {})
|
|
254
|
+
response = @conn.post("#{BASE_URL}#{path}", payload)
|
|
255
|
+
handle_response(response)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def delete(path)
|
|
259
|
+
response = @conn.delete("#{BASE_URL}#{path}")
|
|
260
|
+
return nil if response.status == 204
|
|
261
|
+
handle_response(response)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def handle_response(response)
|
|
265
|
+
case response.status
|
|
266
|
+
when 200..299
|
|
267
|
+
response.body
|
|
268
|
+
when 401
|
|
269
|
+
raise Errors::AuthenticationError, "Invalid Hetzner API token"
|
|
270
|
+
when 404
|
|
271
|
+
raise Errors::NotFoundError, parse_error(response)
|
|
272
|
+
when 422
|
|
273
|
+
raise Errors::ValidationError, parse_error(response)
|
|
274
|
+
else
|
|
275
|
+
raise Errors::ApiError, parse_error(response)
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def parse_error(response)
|
|
280
|
+
if response.body.is_a?(Hash) && response.body["error"]
|
|
281
|
+
response.body["error"]["message"]
|
|
282
|
+
else
|
|
283
|
+
"HTTP #{response.status}: #{response.body}"
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def find_network_by_name(name)
|
|
288
|
+
get("/networks")["networks"].find { |n| n["name"] == name }
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def find_firewall_by_name(name)
|
|
292
|
+
get("/firewalls")["firewalls"].find { |f| f["name"] == name }
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def find_server_by_name(name)
|
|
296
|
+
get("/servers")["servers"].find { |s| s["name"] == name }
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def find_server_type(name)
|
|
300
|
+
get("/server_types")["server_types"].find { |t| t["name"] == name }
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def find_image(name)
|
|
304
|
+
response = get("/images?name=#{name}")
|
|
305
|
+
response["images"]&.first
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def find_location(name)
|
|
309
|
+
get("/locations")["locations"].find { |l| l["name"] == name }
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def create_network_api(payload)
|
|
313
|
+
post("/networks", payload)["network"]
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def create_firewall_api(payload)
|
|
317
|
+
post("/firewalls", payload)["firewall"]
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def remove_firewall_from_server(firewall_id, server_id)
|
|
321
|
+
payload = {
|
|
322
|
+
remove_from: [{
|
|
323
|
+
type: "server",
|
|
324
|
+
server: { id: server_id }
|
|
325
|
+
}]
|
|
326
|
+
}
|
|
327
|
+
post("/firewalls/#{firewall_id}/actions/remove_from_resources", payload)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def detach_server_from_network(server_id, network_id)
|
|
331
|
+
post("/servers/#{server_id}/actions/detach_from_network", { network: network_id })
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def to_network(data)
|
|
335
|
+
Objects::Network::Record.new(
|
|
336
|
+
id: data["id"].to_s,
|
|
337
|
+
name: data["name"],
|
|
338
|
+
ip_range: data["ip_range"]
|
|
339
|
+
)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def to_firewall(data)
|
|
343
|
+
Objects::Firewall::Record.new(
|
|
344
|
+
id: data["id"].to_s,
|
|
345
|
+
name: data["name"]
|
|
346
|
+
)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def to_server(data)
|
|
350
|
+
# Get private IP from private_net array
|
|
351
|
+
private_ip = data["private_net"]&.first&.dig("ip")
|
|
352
|
+
|
|
353
|
+
Objects::Server::Record.new(
|
|
354
|
+
id: data["id"].to_s,
|
|
355
|
+
name: data["name"],
|
|
356
|
+
status: data["status"],
|
|
357
|
+
public_ipv4: data.dig("public_net", "ipv4", "ip"),
|
|
358
|
+
private_ipv4: private_ip
|
|
359
|
+
)
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def to_volume(data)
|
|
363
|
+
Objects::Volume::Record.new(
|
|
364
|
+
id: data["id"].to_s,
|
|
365
|
+
name: data["name"],
|
|
366
|
+
size: data["size"],
|
|
367
|
+
location: data.dig("location", "name"),
|
|
368
|
+
status: data["status"],
|
|
369
|
+
server_id: data["server"]&.to_s,
|
|
370
|
+
device_path: data["linux_device"]
|
|
371
|
+
)
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
end
|