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.
- checksums.yaml +4 -4
- data/.claude/todo/refactor/12-cli-db-command.md +128 -0
- data/.claude/todo/refactor-execution/00-entrypoint.md +49 -0
- data/.claude/todo/refactor-execution/01-objects.md +42 -0
- data/.claude/todo/refactor-execution/02-utils.md +41 -0
- data/.claude/todo/refactor-execution/03-external-cloud.md +38 -0
- data/.claude/todo/refactor-execution/04-external-dns.md +35 -0
- data/.claude/todo/refactor-execution/05-external-other.md +46 -0
- data/.claude/todo/refactor-execution/06-cli-deploy.md +45 -0
- data/.claude/todo/refactor-execution/07-cli-delete.md +43 -0
- data/.claude/todo/refactor-execution/08-cli-exec.md +30 -0
- data/.claude/todo/refactor-execution/09-cli-credentials.md +34 -0
- data/.claude/todo/refactor-execution/10-cli-db.md +31 -0
- data/.claude/todo/refactor-execution/11-cli-router.md +44 -0
- data/.claude/todo/refactor-execution/12-cleanup.md +120 -0
- data/.claude/todo/refactor-execution/_monitoring-strategy.md +126 -0
- data/.claude/todos/buckets.md +41 -0
- data/.claude/todos.md +550 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +35 -4
- data/Rakefile +1 -1
- data/ingest +0 -0
- data/lib/nvoi/cli/config/command.rb +219 -0
- data/lib/nvoi/cli/delete/steps/teardown_dns.rb +12 -11
- data/lib/nvoi/cli/deploy/command.rb +27 -11
- data/lib/nvoi/cli/deploy/steps/build_image.rb +48 -6
- data/lib/nvoi/cli/deploy/steps/configure_tunnel.rb +15 -13
- data/lib/nvoi/cli/deploy/steps/deploy_service.rb +8 -15
- data/lib/nvoi/cli/deploy/steps/setup_k3s.rb +10 -1
- data/lib/nvoi/cli/logs/command.rb +66 -0
- data/lib/nvoi/cli/onboard/command.rb +761 -0
- data/lib/nvoi/cli/unlock/command.rb +72 -0
- data/lib/nvoi/cli.rb +257 -0
- data/lib/nvoi/config_api/actions/app.rb +30 -30
- data/lib/nvoi/config_api/actions/compute_provider.rb +31 -31
- data/lib/nvoi/config_api/actions/database.rb +42 -42
- data/lib/nvoi/config_api/actions/domain_provider.rb +40 -0
- data/lib/nvoi/config_api/actions/env.rb +12 -12
- data/lib/nvoi/config_api/actions/init.rb +67 -0
- data/lib/nvoi/config_api/actions/secret.rb +12 -12
- data/lib/nvoi/config_api/actions/server.rb +35 -35
- data/lib/nvoi/config_api/actions/service.rb +52 -0
- data/lib/nvoi/config_api/actions/volume.rb +18 -18
- data/lib/nvoi/config_api/base.rb +15 -21
- data/lib/nvoi/config_api/result.rb +5 -5
- data/lib/nvoi/config_api.rb +51 -28
- data/lib/nvoi/external/cloud/aws.rb +26 -1
- data/lib/nvoi/external/cloud/hetzner.rb +27 -1
- data/lib/nvoi/external/cloud/scaleway.rb +32 -6
- data/lib/nvoi/external/containerd.rb +1 -44
- data/lib/nvoi/external/dns/cloudflare.rb +34 -16
- data/lib/nvoi/external/ssh.rb +0 -12
- data/lib/nvoi/external/ssh_tunnel.rb +100 -0
- data/lib/nvoi/objects/configuration.rb +20 -0
- data/lib/nvoi/utils/namer.rb +9 -0
- data/lib/nvoi/utils/retry.rb +1 -1
- data/lib/nvoi/version.rb +1 -1
- data/lib/nvoi.rb +16 -0
- data/templates/app-ingress.yaml.erb +3 -1
- 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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
43
|
-
|
|
42
|
+
def mutate(data, name:)
|
|
43
|
+
raise ArgumentError, "name is required" if name.nil? || name.to_s.empty?
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
apps = app(data)["app"] || {}
|
|
46
|
+
raise Errors::ConfigValidationError, "app '#{name}' not found" unless apps.key?(name.to_s)
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
16
|
-
|
|
15
|
+
app(data)["compute_provider"] = { provider.to_s => build_config(provider.to_s, opts) }
|
|
16
|
+
end
|
|
17
17
|
|
|
18
18
|
private
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
17
|
+
validate_server_refs(data, servers)
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
secrets = build_secrets(adapter, user, password, database)
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|