nvoi 0.1.6 → 0.1.8

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/todo/refactor/12-cli-db-command.md +128 -0
  3. data/.claude/todo/refactor-execution/00-entrypoint.md +49 -0
  4. data/.claude/todo/refactor-execution/01-objects.md +42 -0
  5. data/.claude/todo/refactor-execution/02-utils.md +41 -0
  6. data/.claude/todo/refactor-execution/03-external-cloud.md +38 -0
  7. data/.claude/todo/refactor-execution/04-external-dns.md +35 -0
  8. data/.claude/todo/refactor-execution/05-external-other.md +46 -0
  9. data/.claude/todo/refactor-execution/06-cli-deploy.md +45 -0
  10. data/.claude/todo/refactor-execution/07-cli-delete.md +43 -0
  11. data/.claude/todo/refactor-execution/08-cli-exec.md +30 -0
  12. data/.claude/todo/refactor-execution/09-cli-credentials.md +34 -0
  13. data/.claude/todo/refactor-execution/10-cli-db.md +31 -0
  14. data/.claude/todo/refactor-execution/11-cli-router.md +44 -0
  15. data/.claude/todo/refactor-execution/12-cleanup.md +120 -0
  16. data/.claude/todo/refactor-execution/_monitoring-strategy.md +126 -0
  17. data/.claude/todos/buckets.md +41 -0
  18. data/.claude/todos.md +550 -0
  19. data/Gemfile +5 -0
  20. data/Gemfile.lock +35 -4
  21. data/Rakefile +1 -1
  22. data/ingest +0 -0
  23. data/lib/nvoi/cli/config/command.rb +219 -0
  24. data/lib/nvoi/cli/delete/steps/teardown_dns.rb +12 -11
  25. data/lib/nvoi/cli/deploy/command.rb +27 -11
  26. data/lib/nvoi/cli/deploy/steps/build_image.rb +48 -6
  27. data/lib/nvoi/cli/deploy/steps/configure_tunnel.rb +15 -13
  28. data/lib/nvoi/cli/deploy/steps/deploy_service.rb +8 -15
  29. data/lib/nvoi/cli/deploy/steps/setup_k3s.rb +10 -1
  30. data/lib/nvoi/cli/logs/command.rb +66 -0
  31. data/lib/nvoi/cli/onboard/command.rb +761 -0
  32. data/lib/nvoi/cli/unlock/command.rb +72 -0
  33. data/lib/nvoi/cli.rb +257 -0
  34. data/lib/nvoi/config_api/actions/app.rb +30 -30
  35. data/lib/nvoi/config_api/actions/compute_provider.rb +31 -31
  36. data/lib/nvoi/config_api/actions/database.rb +42 -42
  37. data/lib/nvoi/config_api/actions/domain_provider.rb +40 -0
  38. data/lib/nvoi/config_api/actions/env.rb +12 -12
  39. data/lib/nvoi/config_api/actions/init.rb +67 -0
  40. data/lib/nvoi/config_api/actions/secret.rb +12 -12
  41. data/lib/nvoi/config_api/actions/server.rb +35 -35
  42. data/lib/nvoi/config_api/actions/service.rb +52 -0
  43. data/lib/nvoi/config_api/actions/volume.rb +18 -18
  44. data/lib/nvoi/config_api/base.rb +15 -21
  45. data/lib/nvoi/config_api/result.rb +5 -5
  46. data/lib/nvoi/config_api.rb +51 -28
  47. data/lib/nvoi/external/cloud/aws.rb +26 -1
  48. data/lib/nvoi/external/cloud/hetzner.rb +27 -1
  49. data/lib/nvoi/external/cloud/scaleway.rb +32 -6
  50. data/lib/nvoi/external/containerd.rb +1 -44
  51. data/lib/nvoi/external/dns/cloudflare.rb +34 -16
  52. data/lib/nvoi/external/ssh.rb +0 -12
  53. data/lib/nvoi/external/ssh_tunnel.rb +100 -0
  54. data/lib/nvoi/objects/configuration.rb +20 -0
  55. data/lib/nvoi/utils/namer.rb +9 -0
  56. data/lib/nvoi/utils/retry.rb +1 -1
  57. data/lib/nvoi/version.rb +1 -1
  58. data/lib/nvoi.rb +16 -0
  59. data/templates/app-ingress.yaml.erb +3 -1
  60. metadata +27 -1
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nvoi
4
+ class Cli
5
+ module Unlock
6
+ # Command removes deployment lock from remote server
7
+ class Command
8
+ def initialize(options)
9
+ @options = options
10
+ @log = Nvoi.logger
11
+ end
12
+
13
+ def run
14
+ config_path = resolve_config_path
15
+ @config = Utils::ConfigLoader.load(config_path)
16
+
17
+ # Apply branch override if specified
18
+ apply_branch_override if @options[:branch]
19
+
20
+ # Initialize cloud provider
21
+ @provider = External::Cloud.for(@config)
22
+
23
+ # Find main server
24
+ server = @provider.find_server(@config.server_name)
25
+ raise Errors::ServiceError, "server not found: #{@config.server_name}" unless server
26
+
27
+ ssh = External::Ssh.new(server.public_ipv4, @config.ssh_key_path)
28
+ lock_file = @config.namer.deployment_lock_file_path
29
+
30
+ # Check if lock exists and show info
31
+ output = ssh.execute("test -f #{lock_file} && cat #{lock_file} || echo ''").strip
32
+
33
+ if output.empty?
34
+ @log.info "No lock file found: %s", lock_file
35
+ return
36
+ end
37
+
38
+ timestamp = output.to_i
39
+ if timestamp > 0
40
+ lock_time = Time.at(timestamp)
41
+ age = Time.now - lock_time
42
+ @log.info "Lock file age: %ds (since %s)", age.round, lock_time.strftime("%H:%M:%S")
43
+ end
44
+
45
+ ssh.execute("rm -f #{lock_file}")
46
+ @log.success "Removed lock file: %s", lock_file
47
+ end
48
+
49
+ private
50
+
51
+ def resolve_config_path
52
+ config_path = @options[:config] || "deploy.enc"
53
+ working_dir = @options[:dir]
54
+
55
+ if config_path == "deploy.enc" && working_dir && working_dir != "."
56
+ File.join(working_dir, "deploy.enc")
57
+ else
58
+ config_path
59
+ end
60
+ end
61
+
62
+ def apply_branch_override
63
+ branch = @options[:branch]
64
+ return if branch.nil? || branch.empty?
65
+
66
+ override = Objects::ConfigOverride.new(branch:)
67
+ override.apply(@config)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
data/lib/nvoi/cli.rb CHANGED
@@ -21,6 +21,12 @@ module Nvoi
21
21
  puts "nvoi #{VERSION}"
22
22
  end
23
23
 
24
+ desc "onboard", "Interactive setup wizard"
25
+ def onboard
26
+ require_relative "cli/onboard/command"
27
+ Cli::Onboard::Command.new.run
28
+ end
29
+
24
30
  desc "deploy", "Deploy application"
25
31
  option :dockerfile_path, desc: "Path to Dockerfile (optional, defaults to ./Dockerfile)"
26
32
  option :config_dir, desc: "Directory containing SSH keys (optional, defaults to ~/.ssh)"
@@ -36,6 +42,20 @@ module Nvoi
36
42
  Cli::Delete::Command.new(options).run
37
43
  end
38
44
 
45
+ desc "unlock", "Remove deployment lock (use when deploy hangs)"
46
+ def unlock
47
+ require_relative "cli/unlock/command"
48
+ Cli::Unlock::Command.new(options).run
49
+ end
50
+
51
+ desc "logs APP_NAME", "Stream logs from an app"
52
+ option :follow, aliases: "-f", type: :boolean, default: false, desc: "Follow log output"
53
+ option :tail, aliases: "-n", type: :numeric, default: 100, desc: "Number of lines to show"
54
+ def logs(app_name)
55
+ require_relative "cli/logs/command"
56
+ Cli::Logs::Command.new(options).run(app_name)
57
+ end
58
+
39
59
  desc "exec [COMMAND...]", "Execute command on remote server or open interactive shell"
40
60
  option :server, default: "main", desc: "Server to execute on (main, worker-1, worker-2, etc.)"
41
61
  option :all, type: :boolean, default: false, desc: "Execute on all servers"
@@ -75,6 +95,243 @@ module Nvoi
75
95
  end
76
96
  }
77
97
 
98
+ desc "config SUBCOMMAND", "Manage deployment configuration"
99
+ subcommand "config", Class.new(Thor) {
100
+ def self.exit_on_failure?
101
+ true
102
+ end
103
+
104
+ class_option :credentials, desc: "Path to encrypted config file"
105
+ class_option :master_key, desc: "Path to master key file"
106
+ class_option :dir, aliases: "-d", default: ".", desc: "Working directory"
107
+
108
+ desc "init", "Initialize new config"
109
+ option :name, required: true, desc: "Application name"
110
+ option :environment, default: "production", desc: "Environment"
111
+ def init
112
+ require_relative "cli/config/command"
113
+ Nvoi::Cli::Config::Command.new(options).init(options[:name], options[:environment])
114
+ end
115
+
116
+ desc "provider SUBCOMMAND", "Manage compute provider"
117
+ subcommand "provider", Class.new(Thor) {
118
+ def self.exit_on_failure? = true
119
+ class_option :credentials, desc: "Path to encrypted config file"
120
+ class_option :master_key, desc: "Path to master key file"
121
+ class_option :dir, aliases: "-d", default: ".", desc: "Working directory"
122
+
123
+ desc "set PROVIDER", "Set compute provider (hetzner, aws, scaleway)"
124
+ option :api_token, desc: "API token (hetzner)"
125
+ option :server_type, desc: "Server type (cx22, etc)"
126
+ option :server_location, desc: "Location (fsn1, etc)"
127
+ option :access_key_id, desc: "AWS access key ID"
128
+ option :secret_access_key, desc: "AWS secret access key"
129
+ option :region, desc: "AWS region"
130
+ option :instance_type, desc: "AWS instance type"
131
+ option :secret_key, desc: "Scaleway secret key"
132
+ option :project_id, desc: "Scaleway project ID"
133
+ option :zone, desc: "Scaleway zone"
134
+ def set(provider)
135
+ require_relative "cli/config/command"
136
+ Nvoi::Cli::Config::Command.new(options).provider_set(provider, **options.slice(
137
+ :api_token, :server_type, :server_location,
138
+ :access_key_id, :secret_access_key, :region, :instance_type,
139
+ :secret_key, :project_id, :zone
140
+ ).transform_keys(&:to_sym).compact)
141
+ end
142
+
143
+ desc "rm", "Remove compute provider"
144
+ def rm
145
+ require_relative "cli/config/command"
146
+ Nvoi::Cli::Config::Command.new(options).provider_rm
147
+ end
148
+ }
149
+
150
+ desc "domain SUBCOMMAND", "Manage domain provider"
151
+ subcommand "domain", Class.new(Thor) {
152
+ def self.exit_on_failure? = true
153
+ class_option :credentials, desc: "Path to encrypted config file"
154
+ class_option :master_key, desc: "Path to master key file"
155
+ class_option :dir, aliases: "-d", default: ".", desc: "Working directory"
156
+
157
+ desc "set PROVIDER", "Set domain provider (cloudflare)"
158
+ option :api_token, required: true, desc: "API token"
159
+ option :account_id, required: true, desc: "Account ID"
160
+ def set(provider)
161
+ require_relative "cli/config/command"
162
+ Nvoi::Cli::Config::Command.new(options).domain_set(provider, api_token: options[:api_token], account_id: options[:account_id])
163
+ end
164
+
165
+ desc "rm", "Remove domain provider"
166
+ def rm
167
+ require_relative "cli/config/command"
168
+ Nvoi::Cli::Config::Command.new(options).domain_rm
169
+ end
170
+ }
171
+
172
+ desc "server SUBCOMMAND", "Manage servers"
173
+ subcommand "server", Class.new(Thor) {
174
+ def self.exit_on_failure? = true
175
+ class_option :credentials, desc: "Path to encrypted config file"
176
+ class_option :master_key, desc: "Path to master key file"
177
+ class_option :dir, aliases: "-d", default: ".", desc: "Working directory"
178
+
179
+ desc "set NAME", "Add or update server"
180
+ option :master, type: :boolean, default: false, desc: "Set as master server"
181
+ option :type, desc: "Server type override"
182
+ option :location, desc: "Location override"
183
+ option :count, type: :numeric, default: 1, desc: "Number of servers"
184
+ def set(name)
185
+ require_relative "cli/config/command"
186
+ Nvoi::Cli::Config::Command.new(options).server_set(name, master: options[:master], type: options[:type], location: options[:location], count: options[:count])
187
+ end
188
+
189
+ desc "rm NAME", "Remove server"
190
+ def rm(name)
191
+ require_relative "cli/config/command"
192
+ Nvoi::Cli::Config::Command.new(options).server_rm(name)
193
+ end
194
+ }
195
+
196
+ desc "volume SUBCOMMAND", "Manage volumes"
197
+ subcommand "volume", Class.new(Thor) {
198
+ def self.exit_on_failure? = true
199
+ class_option :credentials, desc: "Path to encrypted config file"
200
+ class_option :master_key, desc: "Path to master key file"
201
+ class_option :dir, aliases: "-d", default: ".", desc: "Working directory"
202
+
203
+ desc "set SERVER NAME", "Add or update volume"
204
+ option :size, type: :numeric, default: 10, desc: "Volume size in GB"
205
+ def set(server, name)
206
+ require_relative "cli/config/command"
207
+ Nvoi::Cli::Config::Command.new(options).volume_set(server, name, size: options[:size])
208
+ end
209
+
210
+ desc "rm SERVER NAME", "Remove volume"
211
+ def rm(server, name)
212
+ require_relative "cli/config/command"
213
+ Nvoi::Cli::Config::Command.new(options).volume_rm(server, name)
214
+ end
215
+ }
216
+
217
+ desc "app SUBCOMMAND", "Manage applications"
218
+ subcommand "app", Class.new(Thor) {
219
+ def self.exit_on_failure? = true
220
+ class_option :credentials, desc: "Path to encrypted config file"
221
+ class_option :master_key, desc: "Path to master key file"
222
+ class_option :dir, aliases: "-d", default: ".", desc: "Working directory"
223
+
224
+ desc "set NAME", "Add or update app"
225
+ option :servers, type: :array, required: true, desc: "Server names to run on"
226
+ option :domain, desc: "Domain"
227
+ option :subdomain, desc: "Subdomain"
228
+ option :port, type: :numeric, desc: "Port"
229
+ option :command, desc: "Run command"
230
+ option :pre_run_command, desc: "Pre-run command (migrations, etc)"
231
+ def set(name)
232
+ require_relative "cli/config/command"
233
+ Nvoi::Cli::Config::Command.new(options).app_set(name, **options.slice(:servers, :domain, :subdomain, :port, :command, :pre_run_command).transform_keys(&:to_sym).compact)
234
+ end
235
+
236
+ desc "rm NAME", "Remove app"
237
+ def rm(name)
238
+ require_relative "cli/config/command"
239
+ Nvoi::Cli::Config::Command.new(options).app_rm(name)
240
+ end
241
+ }
242
+
243
+ desc "database SUBCOMMAND", "Manage database"
244
+ subcommand "database", Class.new(Thor) {
245
+ def self.exit_on_failure? = true
246
+ class_option :credentials, desc: "Path to encrypted config file"
247
+ class_option :master_key, desc: "Path to master key file"
248
+ class_option :dir, aliases: "-d", default: ".", desc: "Working directory"
249
+
250
+ desc "set", "Set database configuration"
251
+ option :servers, type: :array, required: true, desc: "Server names"
252
+ option :adapter, required: true, desc: "Database adapter (postgres, mysql, sqlite3)"
253
+ option :user, desc: "Database user"
254
+ option :password, desc: "Database password"
255
+ option :database, desc: "Database name"
256
+ option :url, desc: "Database URL (alternative to user/pass/db)"
257
+ option :image, desc: "Custom Docker image"
258
+ def set
259
+ require_relative "cli/config/command"
260
+ Nvoi::Cli::Config::Command.new(options).database_set(**options.slice(:servers, :adapter, :user, :password, :database, :url, :image).transform_keys(&:to_sym).compact)
261
+ end
262
+
263
+ desc "rm", "Remove database"
264
+ def rm
265
+ require_relative "cli/config/command"
266
+ Nvoi::Cli::Config::Command.new(options).database_rm
267
+ end
268
+ }
269
+
270
+ desc "service SUBCOMMAND", "Manage services (redis, etc)"
271
+ subcommand "service", Class.new(Thor) {
272
+ def self.exit_on_failure? = true
273
+ class_option :credentials, desc: "Path to encrypted config file"
274
+ class_option :master_key, desc: "Path to master key file"
275
+ class_option :dir, aliases: "-d", default: ".", desc: "Working directory"
276
+
277
+ desc "set NAME", "Add or update service"
278
+ option :servers, type: :array, required: true, desc: "Server names"
279
+ option :image, required: true, desc: "Docker image"
280
+ option :port, type: :numeric, desc: "Port"
281
+ option :command, desc: "Command"
282
+ def set(name)
283
+ require_relative "cli/config/command"
284
+ Nvoi::Cli::Config::Command.new(options).service_set(name, **options.slice(:servers, :image, :port, :command).transform_keys(&:to_sym).compact)
285
+ end
286
+
287
+ desc "rm NAME", "Remove service"
288
+ def rm(name)
289
+ require_relative "cli/config/command"
290
+ Nvoi::Cli::Config::Command.new(options).service_rm(name)
291
+ end
292
+ }
293
+
294
+ desc "secret SUBCOMMAND", "Manage secrets"
295
+ subcommand "secret", Class.new(Thor) {
296
+ def self.exit_on_failure? = true
297
+ class_option :credentials, desc: "Path to encrypted config file"
298
+ class_option :master_key, desc: "Path to master key file"
299
+ class_option :dir, aliases: "-d", default: ".", desc: "Working directory"
300
+
301
+ desc "set KEY VALUE", "Set secret"
302
+ def set(key, value)
303
+ require_relative "cli/config/command"
304
+ Nvoi::Cli::Config::Command.new(options).secret_set(key, value)
305
+ end
306
+
307
+ desc "rm KEY", "Remove secret"
308
+ def rm(key)
309
+ require_relative "cli/config/command"
310
+ Nvoi::Cli::Config::Command.new(options).secret_rm(key)
311
+ end
312
+ }
313
+
314
+ desc "env SUBCOMMAND", "Manage environment variables"
315
+ subcommand "env", Class.new(Thor) {
316
+ def self.exit_on_failure? = true
317
+ class_option :credentials, desc: "Path to encrypted config file"
318
+ class_option :master_key, desc: "Path to master key file"
319
+ class_option :dir, aliases: "-d", default: ".", desc: "Working directory"
320
+
321
+ desc "set KEY VALUE", "Set environment variable"
322
+ def set(key, value)
323
+ require_relative "cli/config/command"
324
+ Nvoi::Cli::Config::Command.new(options).env_set(key, value)
325
+ end
326
+
327
+ desc "rm KEY", "Remove environment variable"
328
+ def rm(key)
329
+ require_relative "cli/config/command"
330
+ Nvoi::Cli::Config::Command.new(options).env_rm(key)
331
+ end
332
+ }
333
+ }
334
+
78
335
  desc "db SUBCOMMAND", "Database operations"
79
336
  subcommand "db", Class.new(Thor) {
80
337
  def self.exit_on_failure?
@@ -6,47 +6,47 @@ module Nvoi
6
6
  class SetApp < Base
7
7
  protected
8
8
 
9
- def mutate(data, name:, servers:, domain: nil, subdomain: nil, port: nil, command: nil, pre_run_command: nil, env: nil, mounts: nil)
10
- raise ArgumentError, "name is required" if name.nil? || name.to_s.empty?
11
- raise ArgumentError, "servers is required" if servers.nil? || servers.empty?
12
- raise ArgumentError, "servers must be an array" unless servers.is_a?(Array)
13
-
14
- validate_server_refs(data, servers)
15
-
16
- app(data)["app"] ||= {}
17
- app(data)["app"][name.to_s] = {
18
- "servers" => servers.map(&:to_s),
19
- "domain" => domain,
20
- "subdomain" => subdomain,
21
- "port" => port,
22
- "command" => command,
23
- "pre_run_command" => pre_run_command,
24
- "env" => env,
25
- "mounts" => mounts
26
- }.compact
27
- end
9
+ def mutate(data, name:, servers:, domain: nil, subdomain: nil, port: nil, command: nil, pre_run_command: nil, env: nil, mounts: nil)
10
+ raise ArgumentError, "name is required" if name.nil? || name.to_s.empty?
11
+ raise ArgumentError, "servers is required" if servers.nil? || servers.empty?
12
+ raise ArgumentError, "servers must be an array" unless servers.is_a?(Array)
13
+
14
+ validate_server_refs(data, servers)
15
+
16
+ app(data)["app"] ||= {}
17
+ app(data)["app"][name.to_s] = {
18
+ "servers" => servers.map(&:to_s),
19
+ "domain" => domain,
20
+ "subdomain" => subdomain,
21
+ "port" => port,
22
+ "command" => command,
23
+ "pre_run_command" => pre_run_command,
24
+ "env" => env,
25
+ "mounts" => mounts
26
+ }.compact
27
+ end
28
28
 
29
29
  private
30
30
 
31
- def validate_server_refs(data, servers)
32
- defined = (app(data)["servers"] || {}).keys
33
- servers.each do |ref|
34
- raise Errors::ConfigValidationError, "server '#{ref}' not found" unless defined.include?(ref.to_s)
31
+ def validate_server_refs(data, servers)
32
+ defined = (app(data)["servers"] || {}).keys
33
+ servers.each do |ref|
34
+ raise Errors::ConfigValidationError, "server '#{ref}' not found" unless defined.include?(ref.to_s)
35
+ end
35
36
  end
36
- end
37
37
  end
38
38
 
39
39
  class DeleteApp < Base
40
40
  protected
41
41
 
42
- def mutate(data, name:)
43
- raise ArgumentError, "name is required" if name.nil? || name.to_s.empty?
42
+ def mutate(data, name:)
43
+ raise ArgumentError, "name is required" if name.nil? || name.to_s.empty?
44
44
 
45
- apps = app(data)["app"] || {}
46
- raise Errors::ConfigValidationError, "app '#{name}' not found" unless apps.key?(name.to_s)
45
+ apps = app(data)["app"] || {}
46
+ raise Errors::ConfigValidationError, "app '#{name}' not found" unless apps.key?(name.to_s)
47
47
 
48
- apps.delete(name.to_s)
49
- end
48
+ apps.delete(name.to_s)
49
+ end
50
50
  end
51
51
  end
52
52
  end
@@ -8,47 +8,47 @@ module Nvoi
8
8
 
9
9
  protected
10
10
 
11
- def mutate(data, provider:, **opts)
12
- raise ArgumentError, "provider is required" if provider.nil? || provider.to_s.empty?
13
- raise ArgumentError, "provider must be one of: #{PROVIDERS.join(', ')}" unless PROVIDERS.include?(provider.to_s)
11
+ def mutate(data, provider:, **opts)
12
+ raise ArgumentError, "provider is required" if provider.nil? || provider.to_s.empty?
13
+ raise ArgumentError, "provider must be one of: #{PROVIDERS.join(', ')}" unless PROVIDERS.include?(provider.to_s)
14
14
 
15
- app(data)["compute_provider"] = { provider.to_s => build_config(provider.to_s, opts) }
16
- end
15
+ app(data)["compute_provider"] = { provider.to_s => build_config(provider.to_s, opts) }
16
+ end
17
17
 
18
18
  private
19
19
 
20
- def build_config(provider, opts)
21
- case provider
22
- when "hetzner"
23
- {
24
- "api_token" => opts[:api_token],
25
- "server_type" => opts[:server_type],
26
- "server_location" => opts[:server_location]
27
- }.compact
28
- when "aws"
29
- {
30
- "access_key_id" => opts[:access_key_id],
31
- "secret_access_key" => opts[:secret_access_key],
32
- "region" => opts[:region],
33
- "instance_type" => opts[:instance_type]
34
- }.compact
35
- when "scaleway"
36
- {
37
- "secret_key" => opts[:secret_key],
38
- "project_id" => opts[:project_id],
39
- "zone" => opts[:zone],
40
- "server_type" => opts[:server_type]
41
- }.compact
20
+ def build_config(provider, opts)
21
+ case provider
22
+ when "hetzner"
23
+ {
24
+ "api_token" => opts[:api_token],
25
+ "server_type" => opts[:server_type],
26
+ "server_location" => opts[:server_location]
27
+ }.compact
28
+ when "aws"
29
+ {
30
+ "access_key_id" => opts[:access_key_id],
31
+ "secret_access_key" => opts[:secret_access_key],
32
+ "region" => opts[:region],
33
+ "instance_type" => opts[:instance_type]
34
+ }.compact
35
+ when "scaleway"
36
+ {
37
+ "secret_key" => opts[:secret_key],
38
+ "project_id" => opts[:project_id],
39
+ "zone" => opts[:zone],
40
+ "server_type" => opts[:server_type]
41
+ }.compact
42
+ end
42
43
  end
43
- end
44
44
  end
45
45
 
46
46
  class DeleteComputeProvider < Base
47
47
  protected
48
48
 
49
- def mutate(data, **)
50
- app(data)["compute_provider"] = {}
51
- end
49
+ def mutate(data, **)
50
+ app(data)["compute_provider"] = {}
51
+ end
52
52
  end
53
53
  end
54
54
  end
@@ -8,62 +8,62 @@ module Nvoi
8
8
 
9
9
  protected
10
10
 
11
- def mutate(data, servers:, adapter:, image: nil, url: nil, user: nil, password: nil, database: nil, mount: nil, path: nil)
12
- raise ArgumentError, "servers is required" if servers.nil? || servers.empty?
13
- raise ArgumentError, "servers must be an array" unless servers.is_a?(Array)
14
- raise ArgumentError, "adapter is required" if adapter.nil? || adapter.to_s.empty?
15
- raise ArgumentError, "adapter must be one of: #{ADAPTERS.join(', ')}" unless ADAPTERS.include?(adapter.to_s.downcase)
11
+ def mutate(data, servers:, adapter:, image: nil, url: nil, user: nil, password: nil, database: nil, mount: nil, path: nil)
12
+ raise ArgumentError, "servers is required" if servers.nil? || servers.empty?
13
+ raise ArgumentError, "servers must be an array" unless servers.is_a?(Array)
14
+ raise ArgumentError, "adapter is required" if adapter.nil? || adapter.to_s.empty?
15
+ raise ArgumentError, "adapter must be one of: #{ADAPTERS.join(', ')}" unless ADAPTERS.include?(adapter.to_s.downcase)
16
16
 
17
- validate_server_refs(data, servers)
17
+ validate_server_refs(data, servers)
18
18
 
19
- secrets = build_secrets(adapter, user, password, database)
19
+ secrets = build_secrets(adapter, user, password, database)
20
20
 
21
- app(data)["database"] = {
22
- "servers" => servers.map(&:to_s),
23
- "adapter" => adapter.to_s,
24
- "image" => image,
25
- "url" => url,
26
- "secrets" => secrets.empty? ? nil : secrets,
27
- "mount" => mount,
28
- "path" => path
29
- }.compact
30
- end
21
+ app(data)["database"] = {
22
+ "servers" => servers.map(&:to_s),
23
+ "adapter" => adapter.to_s,
24
+ "image" => image,
25
+ "url" => url,
26
+ "secrets" => secrets.empty? ? nil : secrets,
27
+ "mount" => mount,
28
+ "path" => path
29
+ }.compact
30
+ end
31
31
 
32
32
  private
33
33
 
34
- def validate_server_refs(data, servers)
35
- defined = (app(data)["servers"] || {}).keys
36
- servers.each do |ref|
37
- raise Errors::ConfigValidationError, "server '#{ref}' not found" unless defined.include?(ref.to_s)
34
+ def validate_server_refs(data, servers)
35
+ defined = (app(data)["servers"] || {}).keys
36
+ servers.each do |ref|
37
+ raise Errors::ConfigValidationError, "server '#{ref}' not found" unless defined.include?(ref.to_s)
38
+ end
38
39
  end
39
- end
40
40
 
41
- def build_secrets(adapter, user, password, database)
42
- case adapter.to_s.downcase
43
- when "postgres", "postgresql"
44
- {
45
- "POSTGRES_USER" => user,
46
- "POSTGRES_PASSWORD" => password,
47
- "POSTGRES_DB" => database
48
- }.compact
49
- when "mysql"
50
- {
51
- "MYSQL_USER" => user,
52
- "MYSQL_PASSWORD" => password,
53
- "MYSQL_DATABASE" => database
54
- }.compact
55
- else
56
- {}
41
+ def build_secrets(adapter, user, password, database)
42
+ case adapter.to_s.downcase
43
+ when "postgres", "postgresql"
44
+ {
45
+ "POSTGRES_USER" => user,
46
+ "POSTGRES_PASSWORD" => password,
47
+ "POSTGRES_DB" => database
48
+ }.compact
49
+ when "mysql"
50
+ {
51
+ "MYSQL_USER" => user,
52
+ "MYSQL_PASSWORD" => password,
53
+ "MYSQL_DATABASE" => database
54
+ }.compact
55
+ else
56
+ {}
57
+ end
57
58
  end
58
- end
59
59
  end
60
60
 
61
61
  class DeleteDatabase < Base
62
62
  protected
63
63
 
64
- def mutate(data, **)
65
- app(data).delete("database")
66
- end
64
+ def mutate(data, **)
65
+ app(data).delete("database")
66
+ end
67
67
  end
68
68
  end
69
69
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nvoi
4
+ module ConfigApi
5
+ module Actions
6
+ class SetDomainProvider < Base
7
+ PROVIDERS = %w[cloudflare].freeze
8
+
9
+ protected
10
+
11
+ def mutate(data, provider:, **opts)
12
+ raise ArgumentError, "provider is required" if provider.nil? || provider.to_s.empty?
13
+ raise ArgumentError, "provider must be one of: #{PROVIDERS.join(', ')}" unless PROVIDERS.include?(provider.to_s)
14
+
15
+ app(data)["domain_provider"] = { provider.to_s => build_config(provider.to_s, opts) }
16
+ end
17
+
18
+ private
19
+
20
+ def build_config(provider, opts)
21
+ case provider
22
+ when "cloudflare"
23
+ {
24
+ "api_token" => opts[:api_token],
25
+ "account_id" => opts[:account_id]
26
+ }.compact
27
+ end
28
+ end
29
+ end
30
+
31
+ class DeleteDomainProvider < Base
32
+ protected
33
+
34
+ def mutate(data, **)
35
+ app(data)["domain_provider"] = {}
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end