nvoi 0.1.8 → 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 +4 -4
- data/Gemfile +1 -5
- data/Gemfile.lock +17 -8
- data/Rakefile +1 -1
- data/lib/nvoi/cli/config/command.rb +46 -41
- data/lib/nvoi/cli/credentials/edit/command.rb +20 -20
- data/lib/nvoi/cli/credentials/show/command.rb +1 -1
- data/lib/nvoi/cli/db/command.rb +10 -10
- data/lib/nvoi/cli/delete/command.rb +2 -2
- data/lib/nvoi/cli/deploy/command.rb +2 -2
- data/lib/nvoi/cli/deploy/steps/configure_tunnel.rb +2 -2
- data/lib/nvoi/cli/deploy/steps/provision_server.rb +1 -1
- data/lib/nvoi/cli/deploy/steps/provision_volume.rb +1 -1
- data/lib/nvoi/cli/exec/command.rb +3 -3
- data/lib/nvoi/cli/logs/command.rb +2 -2
- data/lib/nvoi/cli/onboard/command.rb +176 -622
- data/lib/nvoi/cli/onboard/steps/app.rb +108 -0
- data/lib/nvoi/cli/onboard/steps/app_name.rb +26 -0
- data/lib/nvoi/cli/onboard/steps/compute.rb +139 -0
- data/lib/nvoi/cli/onboard/steps/database.rb +97 -0
- data/lib/nvoi/cli/onboard/steps/domain.rb +48 -0
- data/lib/nvoi/cli/onboard/steps/env.rb +67 -0
- data/lib/nvoi/cli/onboard/ui.rb +84 -0
- data/lib/nvoi/cli/unlock/command.rb +2 -2
- data/lib/nvoi/cli.rb +0 -32
- data/lib/nvoi/configuration/app_service.rb +54 -0
- data/lib/nvoi/configuration/application.rb +44 -0
- data/lib/nvoi/configuration/builder.rb +417 -0
- data/lib/nvoi/configuration/database.rb +56 -0
- data/lib/nvoi/configuration/deploy.rb +15 -0
- data/lib/nvoi/{objects/service_spec.rb → configuration/deployment.rb} +4 -3
- data/lib/nvoi/{objects/config_override.rb → configuration/override.rb} +4 -4
- data/lib/nvoi/configuration/providers.rb +78 -0
- data/lib/nvoi/configuration/result.rb +43 -0
- data/lib/nvoi/configuration/root.rb +234 -0
- data/lib/nvoi/configuration/server.rb +39 -0
- data/lib/nvoi/configuration/service.rb +62 -0
- data/lib/nvoi/external/cloud/aws.rb +12 -12
- data/lib/nvoi/external/cloud/hetzner.rb +7 -7
- data/lib/nvoi/external/cloud/scaleway.rb +7 -7
- data/lib/nvoi/external/cloud/types.rb +42 -0
- data/lib/nvoi/external/database/mysql.rb +1 -1
- data/lib/nvoi/external/database/postgres.rb +1 -1
- data/lib/nvoi/external/database/provider.rb +1 -1
- data/lib/nvoi/external/database/sqlite.rb +1 -1
- data/lib/nvoi/external/database/types.rb +55 -0
- data/lib/nvoi/external/dns/cloudflare.rb +6 -6
- data/lib/nvoi/external/dns/types.rb +24 -0
- data/lib/nvoi/utils/config_loader.rb +12 -12
- data/lib/nvoi/utils/credential_store.rb +4 -4
- data/lib/nvoi/utils/env_resolver.rb +3 -3
- data/lib/nvoi/utils/namer.rb +2 -2
- data/lib/nvoi/utils/presence.rb +23 -0
- data/lib/nvoi/version.rb +1 -1
- data/lib/nvoi.rb +2 -17
- metadata +95 -58
- data/.claude/todo/refactor/00-overview.md +0 -171
- data/.claude/todo/refactor/01-objects.md +0 -96
- data/.claude/todo/refactor/02-utils.md +0 -143
- data/.claude/todo/refactor/03-external-cloud.md +0 -164
- data/.claude/todo/refactor/04-external-dns.md +0 -104
- data/.claude/todo/refactor/05-external.md +0 -133
- data/.claude/todo/refactor/06-cli.md +0 -123
- data/.claude/todo/refactor/07-cli-deploy-command.md +0 -177
- data/.claude/todo/refactor/08-cli-deploy-steps.md +0 -201
- data/.claude/todo/refactor/09-cli-delete-command.md +0 -169
- data/.claude/todo/refactor/10-cli-exec-command.md +0 -157
- data/.claude/todo/refactor/11-cli-credentials-command.md +0 -190
- data/.claude/todo/refactor/12-cli-db-command.md +0 -128
- data/.claude/todo/refactor/_target.md +0 -79
- data/.claude/todo/refactor-execution/00-entrypoint.md +0 -49
- data/.claude/todo/refactor-execution/01-objects.md +0 -42
- data/.claude/todo/refactor-execution/02-utils.md +0 -41
- data/.claude/todo/refactor-execution/03-external-cloud.md +0 -38
- data/.claude/todo/refactor-execution/04-external-dns.md +0 -35
- data/.claude/todo/refactor-execution/05-external-other.md +0 -46
- data/.claude/todo/refactor-execution/06-cli-deploy.md +0 -45
- data/.claude/todo/refactor-execution/07-cli-delete.md +0 -43
- data/.claude/todo/refactor-execution/08-cli-exec.md +0 -30
- data/.claude/todo/refactor-execution/09-cli-credentials.md +0 -34
- data/.claude/todo/refactor-execution/10-cli-db.md +0 -31
- data/.claude/todo/refactor-execution/11-cli-router.md +0 -44
- data/.claude/todo/refactor-execution/12-cleanup.md +0 -120
- data/.claude/todo/refactor-execution/_monitoring-strategy.md +0 -126
- data/.claude/todo/scaleway.impl.md +0 -644
- data/.claude/todo/scaleway.reference.md +0 -520
- data/.claude/todos/buckets.md +0 -41
- data/.claude/todos.md +0 -550
- data/ingest +0 -0
- data/lib/nvoi/config_api/actions/app.rb +0 -53
- data/lib/nvoi/config_api/actions/compute_provider.rb +0 -55
- data/lib/nvoi/config_api/actions/database.rb +0 -70
- data/lib/nvoi/config_api/actions/domain_provider.rb +0 -40
- data/lib/nvoi/config_api/actions/env.rb +0 -32
- data/lib/nvoi/config_api/actions/init.rb +0 -67
- data/lib/nvoi/config_api/actions/secret.rb +0 -32
- data/lib/nvoi/config_api/actions/server.rb +0 -66
- data/lib/nvoi/config_api/actions/service.rb +0 -52
- data/lib/nvoi/config_api/actions/volume.rb +0 -40
- data/lib/nvoi/config_api/base.rb +0 -38
- data/lib/nvoi/config_api/result.rb +0 -26
- data/lib/nvoi/config_api.rb +0 -93
- data/lib/nvoi/objects/configuration.rb +0 -483
- data/lib/nvoi/objects/database.rb +0 -56
- data/lib/nvoi/objects/dns.rb +0 -14
- data/lib/nvoi/objects/firewall.rb +0 -11
- data/lib/nvoi/objects/network.rb +0 -11
- data/lib/nvoi/objects/server.rb +0 -14
- data/lib/nvoi/objects/tunnel.rb +0 -14
- 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
|
data/lib/nvoi/objects/dns.rb
DELETED
|
@@ -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
|
data/lib/nvoi/objects/network.rb
DELETED
data/lib/nvoi/objects/server.rb
DELETED
|
@@ -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
|
data/lib/nvoi/objects/tunnel.rb
DELETED
|
@@ -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
|
data/lib/nvoi/objects/volume.rb
DELETED
|
@@ -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
|