nvoi 0.1.7 → 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.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -5
  3. data/Gemfile.lock +17 -8
  4. data/Rakefile +1 -1
  5. data/lib/nvoi/cli/config/command.rb +46 -41
  6. data/lib/nvoi/cli/credentials/edit/command.rb +20 -20
  7. data/lib/nvoi/cli/credentials/show/command.rb +1 -1
  8. data/lib/nvoi/cli/db/command.rb +10 -10
  9. data/lib/nvoi/cli/delete/command.rb +2 -2
  10. data/lib/nvoi/cli/deploy/command.rb +29 -13
  11. data/lib/nvoi/cli/deploy/steps/build_image.rb +48 -6
  12. data/lib/nvoi/cli/deploy/steps/configure_tunnel.rb +2 -2
  13. data/lib/nvoi/cli/deploy/steps/deploy_service.rb +3 -13
  14. data/lib/nvoi/cli/deploy/steps/provision_server.rb +1 -1
  15. data/lib/nvoi/cli/deploy/steps/provision_volume.rb +1 -1
  16. data/lib/nvoi/cli/exec/command.rb +3 -3
  17. data/lib/nvoi/cli/logs/command.rb +2 -2
  18. data/lib/nvoi/cli/onboard/command.rb +176 -622
  19. data/lib/nvoi/cli/onboard/steps/app.rb +108 -0
  20. data/lib/nvoi/cli/onboard/steps/app_name.rb +26 -0
  21. data/lib/nvoi/cli/onboard/steps/compute.rb +139 -0
  22. data/lib/nvoi/cli/onboard/steps/database.rb +97 -0
  23. data/lib/nvoi/cli/onboard/steps/domain.rb +48 -0
  24. data/lib/nvoi/cli/onboard/steps/env.rb +67 -0
  25. data/lib/nvoi/cli/onboard/ui.rb +84 -0
  26. data/lib/nvoi/cli/unlock/command.rb +2 -2
  27. data/lib/nvoi/cli.rb +0 -32
  28. data/lib/nvoi/configuration/app_service.rb +54 -0
  29. data/lib/nvoi/configuration/application.rb +44 -0
  30. data/lib/nvoi/configuration/builder.rb +417 -0
  31. data/lib/nvoi/configuration/database.rb +56 -0
  32. data/lib/nvoi/configuration/deploy.rb +15 -0
  33. data/lib/nvoi/{objects/service_spec.rb → configuration/deployment.rb} +4 -3
  34. data/lib/nvoi/{objects/config_override.rb → configuration/override.rb} +4 -4
  35. data/lib/nvoi/configuration/providers.rb +78 -0
  36. data/lib/nvoi/configuration/result.rb +43 -0
  37. data/lib/nvoi/configuration/root.rb +234 -0
  38. data/lib/nvoi/configuration/server.rb +39 -0
  39. data/lib/nvoi/configuration/service.rb +62 -0
  40. data/lib/nvoi/external/cloud/aws.rb +12 -12
  41. data/lib/nvoi/external/cloud/hetzner.rb +7 -7
  42. data/lib/nvoi/external/cloud/scaleway.rb +7 -7
  43. data/lib/nvoi/external/cloud/types.rb +42 -0
  44. data/lib/nvoi/external/containerd.rb +1 -48
  45. data/lib/nvoi/external/database/mysql.rb +1 -1
  46. data/lib/nvoi/external/database/postgres.rb +1 -1
  47. data/lib/nvoi/external/database/provider.rb +1 -1
  48. data/lib/nvoi/external/database/sqlite.rb +1 -1
  49. data/lib/nvoi/external/database/types.rb +55 -0
  50. data/lib/nvoi/external/dns/cloudflare.rb +6 -6
  51. data/lib/nvoi/external/dns/types.rb +24 -0
  52. data/lib/nvoi/external/ssh.rb +0 -12
  53. data/lib/nvoi/external/ssh_tunnel.rb +100 -0
  54. data/lib/nvoi/utils/config_loader.rb +12 -12
  55. data/lib/nvoi/utils/credential_store.rb +4 -4
  56. data/lib/nvoi/utils/env_resolver.rb +3 -3
  57. data/lib/nvoi/utils/namer.rb +2 -2
  58. data/lib/nvoi/utils/presence.rb +23 -0
  59. data/lib/nvoi/version.rb +1 -1
  60. data/lib/nvoi.rb +2 -17
  61. metadata +96 -57
  62. data/.claude/todo/refactor/00-overview.md +0 -171
  63. data/.claude/todo/refactor/01-objects.md +0 -96
  64. data/.claude/todo/refactor/02-utils.md +0 -143
  65. data/.claude/todo/refactor/03-external-cloud.md +0 -164
  66. data/.claude/todo/refactor/04-external-dns.md +0 -104
  67. data/.claude/todo/refactor/05-external.md +0 -133
  68. data/.claude/todo/refactor/06-cli.md +0 -123
  69. data/.claude/todo/refactor/07-cli-deploy-command.md +0 -177
  70. data/.claude/todo/refactor/08-cli-deploy-steps.md +0 -201
  71. data/.claude/todo/refactor/09-cli-delete-command.md +0 -169
  72. data/.claude/todo/refactor/10-cli-exec-command.md +0 -157
  73. data/.claude/todo/refactor/11-cli-credentials-command.md +0 -190
  74. data/.claude/todo/refactor/12-cli-db-command.md +0 -128
  75. data/.claude/todo/refactor/_target.md +0 -79
  76. data/.claude/todo/refactor-execution/00-entrypoint.md +0 -49
  77. data/.claude/todo/refactor-execution/01-objects.md +0 -42
  78. data/.claude/todo/refactor-execution/02-utils.md +0 -41
  79. data/.claude/todo/refactor-execution/03-external-cloud.md +0 -38
  80. data/.claude/todo/refactor-execution/04-external-dns.md +0 -35
  81. data/.claude/todo/refactor-execution/05-external-other.md +0 -46
  82. data/.claude/todo/refactor-execution/06-cli-deploy.md +0 -45
  83. data/.claude/todo/refactor-execution/07-cli-delete.md +0 -43
  84. data/.claude/todo/refactor-execution/08-cli-exec.md +0 -30
  85. data/.claude/todo/refactor-execution/09-cli-credentials.md +0 -34
  86. data/.claude/todo/refactor-execution/10-cli-db.md +0 -31
  87. data/.claude/todo/refactor-execution/11-cli-router.md +0 -44
  88. data/.claude/todo/refactor-execution/12-cleanup.md +0 -120
  89. data/.claude/todo/refactor-execution/_monitoring-strategy.md +0 -126
  90. data/.claude/todo/scaleway.impl.md +0 -644
  91. data/.claude/todo/scaleway.reference.md +0 -520
  92. data/.claude/todos.md +0 -550
  93. data/ingest +0 -0
  94. data/lib/nvoi/config_api/actions/app.rb +0 -53
  95. data/lib/nvoi/config_api/actions/compute_provider.rb +0 -55
  96. data/lib/nvoi/config_api/actions/database.rb +0 -70
  97. data/lib/nvoi/config_api/actions/domain_provider.rb +0 -40
  98. data/lib/nvoi/config_api/actions/env.rb +0 -32
  99. data/lib/nvoi/config_api/actions/init.rb +0 -67
  100. data/lib/nvoi/config_api/actions/secret.rb +0 -32
  101. data/lib/nvoi/config_api/actions/server.rb +0 -66
  102. data/lib/nvoi/config_api/actions/service.rb +0 -52
  103. data/lib/nvoi/config_api/actions/volume.rb +0 -40
  104. data/lib/nvoi/config_api/base.rb +0 -38
  105. data/lib/nvoi/config_api/result.rb +0 -26
  106. data/lib/nvoi/config_api.rb +0 -93
  107. data/lib/nvoi/objects/configuration.rb +0 -483
  108. data/lib/nvoi/objects/database.rb +0 -56
  109. data/lib/nvoi/objects/dns.rb +0 -14
  110. data/lib/nvoi/objects/firewall.rb +0 -11
  111. data/lib/nvoi/objects/network.rb +0 -11
  112. data/lib/nvoi/objects/server.rb +0 -14
  113. data/lib/nvoi/objects/tunnel.rb +0 -14
  114. data/lib/nvoi/objects/volume.rb +0 -17
@@ -1,483 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Nvoi
4
- module Objects
5
- # Configuration module contains all configuration-related classes
6
- module Configuration
7
- # Root holds the complete configuration including deployment config and runtime settings
8
- class Root
9
- attr_accessor :deploy, :ssh_key_path, :ssh_public_key, :server_name,
10
- :firewall_name, :network_name, :docker_network_name, :container_prefix, :namer
11
-
12
- def initialize(deploy_config)
13
- @deploy = deploy_config
14
- @namer = nil
15
- end
16
-
17
- def namer
18
- @namer ||= Utils::Namer.new(self)
19
- end
20
-
21
- def env_for_service(service_name)
22
- Utils::EnvResolver.new(self).env_for_service(service_name)
23
- end
24
-
25
- def validate_config
26
- app = @deploy.application
27
- validate_providers_config
28
- validate_database_secrets(app.database) if app.database
29
- inject_database_env_vars
30
- validate_service_server_bindings
31
- validate_domain_uniqueness
32
- end
33
-
34
- def provider_name
35
- return "hetzner" if @deploy.application.compute_provider.hetzner
36
- return "aws" if @deploy.application.compute_provider.aws
37
- return "scaleway" if @deploy.application.compute_provider.scaleway
38
-
39
- ""
40
- end
41
-
42
- def hetzner
43
- @deploy.application.compute_provider.hetzner
44
- end
45
-
46
- def aws
47
- @deploy.application.compute_provider.aws
48
- end
49
-
50
- def scaleway
51
- @deploy.application.compute_provider.scaleway
52
- end
53
-
54
- def cloudflare
55
- @deploy.application.domain_provider.cloudflare
56
- end
57
-
58
- def keep_count_value
59
- count = @deploy.application.keep_count
60
- count && count.positive? ? count : 2
61
- end
62
-
63
- private
64
-
65
- def validate_service_server_bindings
66
- app = @deploy.application
67
- defined_servers = {}
68
- master_count = 0
69
-
70
- app.servers.each do |server_name, server_config|
71
- defined_servers[server_name] = true
72
- master_count += 1 if server_config&.master
73
- end
74
-
75
- if app.servers.empty?
76
- has_services = !app.app.empty? || app.database || !app.services.empty?
77
- raise Errors::ConfigValidationError, "servers must be defined when deploying services" if has_services
78
- end
79
-
80
- if app.servers.size > 1
81
- raise Errors::ConfigValidationError, "when multiple servers are defined, exactly one must have master: true" if master_count.zero?
82
- raise Errors::ConfigValidationError, "only one server can have master: true, found #{master_count}" if master_count > 1
83
- elsif app.servers.size == 1 && master_count > 1
84
- raise Errors::ConfigValidationError, "only one server can have master: true, found #{master_count}"
85
- end
86
-
87
- app.app.each do |svc_name, svc_config|
88
- raise Errors::ConfigValidationError, "app.#{svc_name}: servers field is required" if svc_config.servers.empty?
89
-
90
- svc_config.servers.each do |server_ref|
91
- raise Errors::ConfigValidationError, "app.#{svc_name}: references undefined server '#{server_ref}'" unless defined_servers[server_ref]
92
- end
93
- end
94
-
95
- if app.database
96
- raise Errors::ConfigValidationError, "database: servers field is required" if app.database.servers.empty?
97
-
98
- app.database.servers.each do |server_ref|
99
- raise Errors::ConfigValidationError, "database: references undefined server '#{server_ref}'" unless defined_servers[server_ref]
100
- end
101
- end
102
-
103
- app.services.each do |svc_name, svc_config|
104
- raise Errors::ConfigValidationError, "services.#{svc_name}: servers field is required" if svc_config.servers.empty?
105
-
106
- svc_config.servers.each do |server_ref|
107
- raise Errors::ConfigValidationError, "services.#{svc_name}: references undefined server '#{server_ref}'" unless defined_servers[server_ref]
108
- end
109
- end
110
- end
111
-
112
- def validate_database_secrets(db)
113
- adapter = db.adapter&.downcase
114
- return if db.url && !db.url.empty?
115
-
116
- case adapter
117
- when "postgres", "postgresql"
118
- %w[POSTGRES_USER POSTGRES_PASSWORD POSTGRES_DB].each do |key|
119
- raise Errors::ConfigValidationError, "postgres database requires #{key} in secrets (or provide database.url)" unless db.secrets[key]
120
- end
121
- when "mysql"
122
- %w[MYSQL_USER MYSQL_PASSWORD MYSQL_DATABASE].each do |key|
123
- raise Errors::ConfigValidationError, "mysql database requires #{key} in secrets (or provide database.url)" unless db.secrets[key]
124
- end
125
- when "sqlite", "sqlite3"
126
- # SQLite doesn't require secrets
127
- end
128
- end
129
-
130
- def validate_providers_config
131
- app = @deploy.application
132
-
133
- unless app.domain_provider.cloudflare
134
- raise Errors::ConfigValidationError, "domain provider required: currently only cloudflare is supported"
135
- end
136
-
137
- cf = app.domain_provider.cloudflare
138
- raise Errors::ConfigValidationError, "cloudflare api_token is required" if cf.api_token.nil? || cf.api_token.empty?
139
- raise Errors::ConfigValidationError, "cloudflare account_id is required" if cf.account_id.nil? || cf.account_id.empty?
140
-
141
- has_provider = false
142
-
143
- if app.compute_provider.hetzner
144
- has_provider = true
145
- h = app.compute_provider.hetzner
146
- raise Errors::ConfigValidationError, "hetzner api_token is required" if h.api_token.nil? || h.api_token.empty?
147
- raise Errors::ConfigValidationError, "hetzner server_type is required" if h.server_type.nil? || h.server_type.empty?
148
- raise Errors::ConfigValidationError, "hetzner server_location is required" if h.server_location.nil? || h.server_location.empty?
149
- end
150
-
151
- if app.compute_provider.aws
152
- has_provider = true
153
- a = app.compute_provider.aws
154
- raise Errors::ConfigValidationError, "aws access_key_id is required" if a.access_key_id.nil? || a.access_key_id.empty?
155
- raise Errors::ConfigValidationError, "aws secret_access_key is required" if a.secret_access_key.nil? || a.secret_access_key.empty?
156
- raise Errors::ConfigValidationError, "aws region is required" if a.region.nil? || a.region.empty?
157
- raise Errors::ConfigValidationError, "aws instance_type is required" if a.instance_type.nil? || a.instance_type.empty?
158
- end
159
-
160
- if app.compute_provider.scaleway
161
- has_provider = true
162
- s = app.compute_provider.scaleway
163
- raise Errors::ConfigValidationError, "scaleway secret_key is required" if s.secret_key.nil? || s.secret_key.empty?
164
- raise Errors::ConfigValidationError, "scaleway project_id is required" if s.project_id.nil? || s.project_id.empty?
165
- raise Errors::ConfigValidationError, "scaleway server_type is required" if s.server_type.nil? || s.server_type.empty?
166
- end
167
-
168
- raise Errors::ConfigValidationError, "compute provider required: hetzner, aws, or scaleway must be configured" unless has_provider
169
- end
170
-
171
- def inject_database_env_vars
172
- app = @deploy.application
173
- return unless app.database
174
-
175
- db = app.database
176
- adapter = db.adapter&.downcase
177
- return unless adapter
178
-
179
- provider = External::Database.provider_for(adapter)
180
- return unless provider.needs_container?
181
-
182
- creds = parse_database_credentials(db, provider)
183
- return unless creds
184
-
185
- db_host = namer.database_service_name
186
- env_vars = provider.app_env(creds, host: db_host)
187
-
188
- app.app.each_value do |svc_config|
189
- svc_config.env ||= {}
190
- env_vars.each { |key, value| svc_config.env[key] ||= value }
191
- end
192
- end
193
-
194
- def parse_database_credentials(db, provider)
195
- return provider.parse_url(db.url) if db.url && !db.url.empty?
196
-
197
- adapter = db.adapter&.downcase
198
- case adapter
199
- when "postgres", "postgresql"
200
- Database::Credentials.new(
201
- user: db.secrets["POSTGRES_USER"],
202
- password: db.secrets["POSTGRES_PASSWORD"],
203
- database: db.secrets["POSTGRES_DB"],
204
- port: provider.default_port
205
- )
206
- when "mysql"
207
- Database::Credentials.new(
208
- user: db.secrets["MYSQL_USER"],
209
- password: db.secrets["MYSQL_PASSWORD"],
210
- database: db.secrets["MYSQL_DATABASE"],
211
- port: provider.default_port
212
- )
213
- end
214
- end
215
-
216
- def validate_domain_uniqueness
217
- app = @deploy.application
218
- return unless app.app
219
-
220
- seen = {}
221
- app.app.each do |name, cfg|
222
- next unless cfg.domain && !cfg.domain.empty?
223
-
224
- hostnames = Utils::Namer.build_hostnames(cfg.subdomain, cfg.domain)
225
- hostnames.each do |hostname|
226
- if seen[hostname]
227
- raise Errors::ConfigValidationError,
228
- "domain '#{hostname}' used by both '#{seen[hostname]}' and '#{name}'"
229
- end
230
- seen[hostname] = name
231
- end
232
- end
233
- end
234
- end
235
-
236
- # Deploy represents the root deployment configuration
237
- class Deploy
238
- attr_accessor :application
239
-
240
- def initialize(data = {})
241
- @application = Application.new(data["application"] || {})
242
- end
243
- end
244
-
245
- # Application contains application-level configuration
246
- class Application
247
- attr_accessor :name, :environment, :domain_provider, :compute_provider,
248
- :keep_count, :servers, :app, :database, :services, :env,
249
- :secrets, :ssh_keys
250
-
251
- def initialize(data = {})
252
- @name = data["name"]
253
- @environment = data["environment"] || "production"
254
- @domain_provider = DomainProvider.new(data["domain_provider"] || {})
255
- @compute_provider = ComputeProvider.new(data["compute_provider"] || {})
256
- @keep_count = data["keep_count"]&.to_i
257
- @servers = (data["servers"] || {}).transform_values { |v| Server.new(v || {}) }
258
- @app = (data["app"] || {}).transform_values { |v| AppService.new(v || {}) }
259
- @database = data["database"] ? DatabaseCfg.new(data["database"]) : nil
260
- @services = (data["services"] || {}).transform_values { |v| Service.new(v || {}) }
261
- @env = data["env"] || {}
262
- @secrets = data["secrets"] || {}
263
- @ssh_keys = data["ssh_keys"] ? SshKey.new(data["ssh_keys"]) : nil
264
- end
265
- end
266
-
267
- # DomainProvider contains domain provider configuration
268
- class DomainProvider
269
- attr_accessor :cloudflare
270
-
271
- def initialize(data = {})
272
- @cloudflare = data["cloudflare"] ? Cloudflare.new(data["cloudflare"]) : nil
273
- end
274
- end
275
-
276
- # ComputeProvider contains compute provider configuration
277
- class ComputeProvider
278
- attr_accessor :hetzner, :aws, :scaleway
279
-
280
- def initialize(data = {})
281
- @hetzner = data["hetzner"] ? Hetzner.new(data["hetzner"]) : nil
282
- @aws = data["aws"] ? AwsCfg.new(data["aws"]) : nil
283
- @scaleway = data["scaleway"] ? Scaleway.new(data["scaleway"]) : nil
284
- end
285
- end
286
-
287
- # Cloudflare contains Cloudflare-specific configuration
288
- class Cloudflare
289
- attr_accessor :api_token, :account_id
290
-
291
- def initialize(data = {})
292
- @api_token = data["api_token"]
293
- @account_id = data["account_id"]
294
- end
295
- end
296
-
297
- # Hetzner contains Hetzner-specific configuration
298
- class Hetzner
299
- attr_accessor :api_token, :server_type, :server_location
300
-
301
- def initialize(data = {})
302
- @api_token = data["api_token"]
303
- @server_type = data["server_type"]
304
- @server_location = data["server_location"]
305
- end
306
- end
307
-
308
- # AwsCfg contains AWS-specific configuration
309
- class AwsCfg
310
- attr_accessor :access_key_id, :secret_access_key, :region, :instance_type
311
-
312
- def initialize(data = {})
313
- @access_key_id = data["access_key_id"]
314
- @secret_access_key = data["secret_access_key"]
315
- @region = data["region"]
316
- @instance_type = data["instance_type"]
317
- end
318
- end
319
-
320
- # Scaleway contains Scaleway-specific configuration
321
- class Scaleway
322
- attr_accessor :secret_key, :project_id, :zone, :server_type
323
-
324
- def initialize(data = {})
325
- @secret_key = data["secret_key"]
326
- @project_id = data["project_id"]
327
- @zone = data["zone"] || "fr-par-1"
328
- @server_type = data["server_type"]
329
- end
330
- end
331
-
332
- # ServerVolume defines a volume attached to a server
333
- class ServerVolume
334
- attr_accessor :size
335
-
336
- def initialize(data = {})
337
- raise ArgumentError, "volume config must be a hash with 'size' key" unless data.is_a?(Hash)
338
-
339
- @size = data["size"]&.to_i || 10
340
- end
341
- end
342
-
343
- # Server contains server instance configuration
344
- class Server
345
- attr_accessor :master, :type, :location, :count, :volumes
346
-
347
- def initialize(data = {})
348
- @master = data["master"] || false
349
- @type = data["type"]
350
- @location = data["location"]
351
- @count = data["count"]&.to_i || 1
352
- @volumes = (data["volumes"] || {}).transform_values { |v| ServerVolume.new(v || {}) }
353
- end
354
- end
355
-
356
- # AppService defines a service in the app section
357
- class AppService
358
- attr_accessor :servers, :domain, :subdomain, :port, :healthcheck,
359
- :command, :pre_run_command, :env, :mounts
360
-
361
- def initialize(data = {})
362
- @servers = data["servers"] || []
363
- @domain = data["domain"]
364
- @subdomain = data["subdomain"]
365
- @port = data["port"]&.to_i
366
- @healthcheck = data["healthcheck"] ? HealthCheck.new(data["healthcheck"]) : nil
367
- @command = data["command"]
368
- @pre_run_command = data["pre_run_command"]
369
- @env = data["env"] || {}
370
- @mounts = data["mounts"] || {}
371
- end
372
- end
373
-
374
- # HealthCheck defines health check configuration
375
- class HealthCheck
376
- attr_accessor :type, :path, :port, :command, :interval, :timeout, :retries
377
-
378
- def initialize(data = {})
379
- @type = data["type"]
380
- @path = data["path"]
381
- @port = data["port"]&.to_i
382
- @command = data["command"]
383
- @interval = data["interval"]
384
- @timeout = data["timeout"]
385
- @retries = data["retries"]&.to_i
386
- end
387
- end
388
-
389
- # DatabaseCfg defines database configuration
390
- class DatabaseCfg
391
- attr_accessor :servers, :adapter, :url, :image, :mount, :secrets, :path
392
-
393
- def initialize(data = {})
394
- @servers = data["servers"] || []
395
- @adapter = data["adapter"]
396
- @url = data["url"]
397
- @image = data["image"]
398
- @mount = data["mount"] || {}
399
- @secrets = data["secrets"] || {}
400
- @path = data["path"]
401
- end
402
-
403
- def to_service_spec(namer)
404
- return nil if @adapter&.downcase&.start_with?("sqlite")
405
-
406
- port = case @adapter&.downcase
407
- when "mysql" then 3306
408
- else 5432
409
- end
410
-
411
- image = @image || Utils::Constants::DATABASE_IMAGES[@adapter&.downcase]
412
-
413
- ServiceSpec.new(
414
- name: namer.database_service_name,
415
- image:,
416
- port:,
417
- env: nil,
418
- mounts: @mount,
419
- replicas: 1,
420
- stateful_set: true,
421
- secrets: @secrets,
422
- servers: @servers
423
- )
424
- end
425
- end
426
-
427
- # Service defines a generic service
428
- class Service
429
- attr_accessor :servers, :image, :port, :command, :env, :mount
430
-
431
- def initialize(data = {})
432
- @servers = data["servers"] || []
433
- @image = data["image"]
434
- @port = data["port"]&.to_i
435
- @command = data["command"]
436
- @env = data["env"] || {}
437
- @mount = data["mount"] || {}
438
- end
439
-
440
- def to_service_spec(app_name, service_name)
441
- cmd = @command ? @command.split : []
442
- port = @port && @port.positive? ? @port : infer_port_from_image
443
-
444
- ServiceSpec.new(
445
- name: "#{app_name}-#{service_name}",
446
- image: @image,
447
- port:,
448
- command: cmd,
449
- env: @env,
450
- mounts: @mount,
451
- replicas: 1,
452
- stateful_set: false,
453
- servers: @servers
454
- )
455
- end
456
-
457
- private
458
-
459
- def infer_port_from_image
460
- case @image
461
- when /redis/ then 6379
462
- when /postgres/ then 5432
463
- when /mysql/ then 3306
464
- when /memcache/ then 11211
465
- when /mongo/ then 27017
466
- when /elastic/ then 9200
467
- else 0
468
- end
469
- end
470
- end
471
-
472
- # SshKey defines SSH key content (stored in encrypted config)
473
- class SshKey
474
- attr_accessor :private_key, :public_key
475
-
476
- def initialize(data = {})
477
- @private_key = data["private_key"]
478
- @public_key = data["public_key"]
479
- end
480
- end
481
- end
482
- end
483
- end
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
-
5
- module Nvoi
6
- module Objects
7
- # Database-related structs and classes
8
- module Database
9
- # Parsed credentials from database URL
10
- Credentials = Struct.new(:user, :password, :host, :port, :database, :path, :host_path, keyword_init: true)
11
-
12
- # Options for dumping a database
13
- DumpOptions = Struct.new(:pod_name, :database, :user, :password, :host_path, keyword_init: true)
14
-
15
- # Options for restoring a database
16
- RestoreOptions = Struct.new(:pod_name, :database, :user, :password, :source_db, :host_path, keyword_init: true)
17
-
18
- # Options for creating a database
19
- CreateOptions = Struct.new(:pod_name, :database, :user, :password, keyword_init: true)
20
-
21
- # Branch represents a database branch (snapshot)
22
- Branch = Struct.new(:id, :created_at, :size, :adapter, :database, keyword_init: true) do
23
- def to_h
24
- { id:, created_at:, size:, adapter:, database: }
25
- end
26
- end
27
-
28
- # BranchMetadata holds all branches for an app
29
- class BranchMetadata
30
- attr_accessor :branches
31
-
32
- def initialize(branches = [])
33
- @branches = branches
34
- end
35
-
36
- def to_json(*_args)
37
- JSON.pretty_generate({ branches: @branches.map(&:to_h) })
38
- end
39
-
40
- def self.from_json(json_str)
41
- data = JSON.parse(json_str)
42
- branches = (data["branches"] || []).map do |b|
43
- Branch.new(
44
- id: b["id"],
45
- created_at: b["created_at"],
46
- size: b["size"],
47
- adapter: b["adapter"],
48
- database: b["database"]
49
- )
50
- end
51
- new(branches)
52
- end
53
- end
54
- end
55
- end
56
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Nvoi
4
- module Objects
5
- # DNS-related structs
6
- module Dns
7
- # Zone represents a Cloudflare DNS zone
8
- Zone = Struct.new(:id, :name, keyword_init: true)
9
-
10
- # Record represents a Cloudflare DNS record
11
- Record = Struct.new(:id, :type, :name, :content, :proxied, :ttl, keyword_init: true)
12
- end
13
- end
14
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Nvoi
4
- module Objects
5
- # Firewall-related structs
6
- module Firewall
7
- # Record represents a firewall configuration
8
- Record = Struct.new(:id, :name, keyword_init: true)
9
- end
10
- end
11
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Nvoi
4
- module Objects
5
- # Network-related structs
6
- module Network
7
- # Record represents a virtual network
8
- Record = Struct.new(:id, :name, :ip_range, keyword_init: true)
9
- end
10
- end
11
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Nvoi
4
- module Objects
5
- # Server-related structs
6
- module Server
7
- # Record represents a compute server/instance
8
- Record = Struct.new(:id, :name, :status, :public_ipv4, :private_ipv4, keyword_init: true)
9
-
10
- # CreateOptions contains options for creating a server
11
- CreateOptions = Struct.new(:name, :type, :image, :location, :user_data, :network_id, :firewall_id, :ssh_keys, keyword_init: true)
12
- end
13
- end
14
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Nvoi
4
- module Objects
5
- # Tunnel-related structs
6
- module Tunnel
7
- # Record represents a Cloudflare tunnel
8
- Record = Struct.new(:id, :name, :token, keyword_init: true)
9
-
10
- # Info holds information about a configured tunnel
11
- Info = Struct.new(:service_name, :hostname, :tunnel_id, :tunnel_token, :port, keyword_init: true)
12
- end
13
- end
14
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Nvoi
4
- module Objects
5
- # Volume-related structs
6
- module Volume
7
- # Volume represents a block storage volume
8
- Record = Struct.new(:id, :name, :size, :location, :status, :server_id, :device_path, keyword_init: true)
9
-
10
- # CreateOptions contains options for creating a volume
11
- CreateOptions = Struct.new(:name, :size, :server_id, :location, keyword_init: true)
12
-
13
- # MountOptions contains options for mounting a volume
14
- MountOptions = Struct.new(:device_path, :mount_path, :fs_type, keyword_init: true)
15
- end
16
- end
17
- end