nvoi 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/todo/refactor/00-overview.md +171 -0
- data/.claude/todo/refactor/01-objects.md +96 -0
- data/.claude/todo/refactor/02-utils.md +143 -0
- data/.claude/todo/refactor/03-external-cloud.md +164 -0
- data/.claude/todo/refactor/04-external-dns.md +104 -0
- data/.claude/todo/refactor/05-external.md +133 -0
- data/.claude/todo/refactor/06-cli.md +123 -0
- data/.claude/todo/refactor/07-cli-deploy-command.md +177 -0
- data/.claude/todo/refactor/08-cli-deploy-steps.md +201 -0
- data/.claude/todo/refactor/09-cli-delete-command.md +169 -0
- data/.claude/todo/refactor/10-cli-exec-command.md +157 -0
- data/.claude/todo/refactor/11-cli-credentials-command.md +190 -0
- data/.claude/todo/refactor/12-cli-db-command.md +128 -0
- data/.claude/todo/refactor/_target.md +79 -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/todo/scaleway.impl.md +644 -0
- data/.claude/todo/scaleway.reference.md +520 -0
- data/.claude/todos.md +550 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +46 -5
- data/Rakefile +1 -1
- data/doc/config-schema.yaml +44 -11
- data/examples/golang/deploy.enc +0 -0
- data/examples/golang/main.go +18 -0
- data/exe/nvoi +3 -1
- data/ingest +0 -0
- data/lib/nvoi/cli/config/command.rb +219 -0
- data/lib/nvoi/cli/credentials/edit/command.rb +384 -0
- data/lib/nvoi/cli/credentials/show/command.rb +35 -0
- data/lib/nvoi/cli/db/command.rb +308 -0
- data/lib/nvoi/cli/delete/command.rb +75 -0
- data/lib/nvoi/cli/delete/steps/detach_volumes.rb +98 -0
- data/lib/nvoi/cli/delete/steps/teardown_dns.rb +50 -0
- data/lib/nvoi/cli/delete/steps/teardown_firewall.rb +46 -0
- data/lib/nvoi/cli/delete/steps/teardown_network.rb +30 -0
- data/lib/nvoi/cli/delete/steps/teardown_server.rb +50 -0
- data/lib/nvoi/cli/delete/steps/teardown_tunnel.rb +44 -0
- data/lib/nvoi/cli/delete/steps/teardown_volume.rb +61 -0
- data/lib/nvoi/cli/deploy/command.rb +184 -0
- data/lib/nvoi/cli/deploy/steps/build_image.rb +27 -0
- data/lib/nvoi/cli/deploy/steps/cleanup_images.rb +42 -0
- data/lib/nvoi/cli/deploy/steps/configure_tunnel.rb +102 -0
- data/lib/nvoi/cli/deploy/steps/deploy_service.rb +399 -0
- data/lib/nvoi/cli/deploy/steps/provision_network.rb +44 -0
- data/lib/nvoi/cli/deploy/steps/provision_server.rb +143 -0
- data/lib/nvoi/cli/deploy/steps/provision_volume.rb +171 -0
- data/lib/nvoi/cli/deploy/steps/setup_k3s.rb +490 -0
- data/lib/nvoi/cli/exec/command.rb +173 -0
- 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 +339 -141
- data/lib/nvoi/config_api/actions/app.rb +53 -0
- data/lib/nvoi/config_api/actions/compute_provider.rb +55 -0
- data/lib/nvoi/config_api/actions/database.rb +70 -0
- data/lib/nvoi/config_api/actions/domain_provider.rb +40 -0
- data/lib/nvoi/config_api/actions/env.rb +32 -0
- data/lib/nvoi/config_api/actions/init.rb +67 -0
- data/lib/nvoi/config_api/actions/secret.rb +32 -0
- data/lib/nvoi/config_api/actions/server.rb +66 -0
- data/lib/nvoi/config_api/actions/service.rb +52 -0
- data/lib/nvoi/config_api/actions/volume.rb +40 -0
- data/lib/nvoi/config_api/base.rb +38 -0
- data/lib/nvoi/config_api/result.rb +26 -0
- data/lib/nvoi/config_api.rb +93 -0
- data/lib/nvoi/errors.rb +68 -50
- data/lib/nvoi/external/cloud/aws.rb +450 -0
- data/lib/nvoi/external/cloud/base.rb +99 -0
- data/lib/nvoi/external/cloud/factory.rb +48 -0
- data/lib/nvoi/external/cloud/hetzner.rb +402 -0
- data/lib/nvoi/external/cloud/scaleway.rb +559 -0
- data/lib/nvoi/external/cloud.rb +15 -0
- data/lib/nvoi/external/containerd.rb +86 -0
- data/lib/nvoi/external/database/mysql.rb +84 -0
- data/lib/nvoi/external/database/postgres.rb +82 -0
- data/lib/nvoi/external/database/provider.rb +65 -0
- data/lib/nvoi/external/database/sqlite.rb +72 -0
- data/lib/nvoi/external/database.rb +22 -0
- data/lib/nvoi/external/dns/cloudflare.rb +310 -0
- data/lib/nvoi/external/kubectl.rb +65 -0
- data/lib/nvoi/external/ssh.rb +106 -0
- data/lib/nvoi/objects/config_override.rb +60 -0
- data/lib/nvoi/objects/configuration.rb +483 -0
- data/lib/nvoi/objects/database.rb +56 -0
- data/lib/nvoi/objects/dns.rb +14 -0
- data/lib/nvoi/objects/firewall.rb +11 -0
- data/lib/nvoi/objects/network.rb +11 -0
- data/lib/nvoi/objects/server.rb +14 -0
- data/lib/nvoi/objects/service_spec.rb +26 -0
- data/lib/nvoi/objects/tunnel.rb +14 -0
- data/lib/nvoi/objects/volume.rb +17 -0
- data/lib/nvoi/utils/config_loader.rb +172 -0
- data/lib/nvoi/utils/constants.rb +61 -0
- data/lib/nvoi/{credentials/manager.rb → utils/credential_store.rb} +16 -16
- data/lib/nvoi/{credentials → utils}/crypto.rb +8 -5
- data/lib/nvoi/{config → utils}/env_resolver.rb +10 -2
- data/lib/nvoi/utils/logger.rb +84 -0
- data/lib/nvoi/{config/naming.rb → utils/namer.rb} +37 -25
- data/lib/nvoi/{deployer → utils}/retry.rb +23 -3
- data/lib/nvoi/utils/templates.rb +62 -0
- data/lib/nvoi/version.rb +1 -1
- data/lib/nvoi.rb +27 -55
- data/templates/app-ingress.yaml.erb +3 -1
- data/templates/error-backend.yaml.erb +134 -0
- metadata +121 -44
- data/examples/golang/deploy.yml +0 -54
- data/lib/nvoi/cloudflare/client.rb +0 -287
- data/lib/nvoi/config/config.rb +0 -248
- data/lib/nvoi/config/loader.rb +0 -102
- data/lib/nvoi/config/ssh_keys.rb +0 -82
- data/lib/nvoi/config/types.rb +0 -274
- data/lib/nvoi/constants.rb +0 -59
- data/lib/nvoi/credentials/editor.rb +0 -272
- data/lib/nvoi/deployer/cleaner.rb +0 -36
- data/lib/nvoi/deployer/image_builder.rb +0 -23
- data/lib/nvoi/deployer/infrastructure.rb +0 -126
- data/lib/nvoi/deployer/orchestrator.rb +0 -146
- data/lib/nvoi/deployer/service_deployer.rb +0 -311
- data/lib/nvoi/deployer/tunnel_manager.rb +0 -57
- data/lib/nvoi/deployer/types.rb +0 -8
- data/lib/nvoi/k8s/renderer.rb +0 -44
- data/lib/nvoi/k8s/templates.rb +0 -29
- data/lib/nvoi/logger.rb +0 -72
- data/lib/nvoi/providers/aws.rb +0 -403
- data/lib/nvoi/providers/base.rb +0 -111
- data/lib/nvoi/providers/hetzner.rb +0 -288
- data/lib/nvoi/providers/hetzner_client.rb +0 -170
- data/lib/nvoi/remote/docker_manager.rb +0 -203
- data/lib/nvoi/remote/ssh_executor.rb +0 -72
- data/lib/nvoi/remote/volume_manager.rb +0 -103
- data/lib/nvoi/service/delete.rb +0 -234
- data/lib/nvoi/service/deploy.rb +0 -80
- data/lib/nvoi/service/exec.rb +0 -144
- data/lib/nvoi/service/provider.rb +0 -36
- data/lib/nvoi/steps/application_deployer.rb +0 -26
- data/lib/nvoi/steps/database_provisioner.rb +0 -60
- data/lib/nvoi/steps/k3s_cluster_setup.rb +0 -105
- data/lib/nvoi/steps/k3s_provisioner.rb +0 -351
- data/lib/nvoi/steps/server_provisioner.rb +0 -43
- data/lib/nvoi/steps/services_provisioner.rb +0 -29
- data/lib/nvoi/steps/tunnel_configurator.rb +0 -66
- data/lib/nvoi/steps/volume_provisioner.rb +0 -154
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nvoi
|
|
4
|
+
class Cli
|
|
5
|
+
module Exec
|
|
6
|
+
# Command handles remote command execution on servers
|
|
7
|
+
class Command
|
|
8
|
+
def initialize(options)
|
|
9
|
+
@options = options
|
|
10
|
+
@log = Nvoi.logger
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def run(args)
|
|
14
|
+
@log.info "Exec CLI %s", VERSION
|
|
15
|
+
|
|
16
|
+
# Load configuration
|
|
17
|
+
config_path = resolve_config_path
|
|
18
|
+
@config = Utils::ConfigLoader.load(config_path)
|
|
19
|
+
|
|
20
|
+
# Apply branch override if specified
|
|
21
|
+
apply_branch_override if @options[:branch]
|
|
22
|
+
|
|
23
|
+
# Initialize cloud provider
|
|
24
|
+
@provider = External::Cloud.for(@config)
|
|
25
|
+
|
|
26
|
+
if @options[:interactive]
|
|
27
|
+
@log.warning "Ignoring command arguments in interactive mode" unless args.empty?
|
|
28
|
+
@log.warning "Ignoring --all flag in interactive mode" if @options[:all]
|
|
29
|
+
open_shell(@options[:server])
|
|
30
|
+
else
|
|
31
|
+
raise ArgumentError, "command required (use --interactive/-i for shell)" if args.empty?
|
|
32
|
+
|
|
33
|
+
command = args.join(" ")
|
|
34
|
+
|
|
35
|
+
if @options[:all]
|
|
36
|
+
run_all(command)
|
|
37
|
+
else
|
|
38
|
+
run_on_server(command, @options[:server])
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def resolve_config_path
|
|
46
|
+
config_path = @options[:config] || "deploy.enc"
|
|
47
|
+
working_dir = @options[:dir]
|
|
48
|
+
|
|
49
|
+
if config_path == "deploy.enc" && working_dir && working_dir != "."
|
|
50
|
+
File.join(working_dir, "deploy.enc")
|
|
51
|
+
else
|
|
52
|
+
config_path
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def apply_branch_override
|
|
57
|
+
branch = @options[:branch]
|
|
58
|
+
return if branch.nil? || branch.empty?
|
|
59
|
+
|
|
60
|
+
override = Objects::ConfigOverride.new(branch:)
|
|
61
|
+
override.apply(@config)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def run_on_server(command, server_name)
|
|
65
|
+
actual_server_name = resolve_server_name(server_name)
|
|
66
|
+
server = find_server(actual_server_name)
|
|
67
|
+
|
|
68
|
+
@log.info "Connecting to %s (%s)", actual_server_name, server.public_ipv4
|
|
69
|
+
|
|
70
|
+
ssh = External::Ssh.new(server.public_ipv4, @config.ssh_key_path)
|
|
71
|
+
|
|
72
|
+
@log.info "Executing: %s", command
|
|
73
|
+
output = ssh.execute(command, stream: true)
|
|
74
|
+
|
|
75
|
+
puts output if !output.empty? && !output.include?("\n")
|
|
76
|
+
|
|
77
|
+
@log.success "Command completed successfully"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def run_all(command)
|
|
81
|
+
server_names = get_all_server_names
|
|
82
|
+
|
|
83
|
+
raise Errors::ServiceError, "no servers found in configuration" if server_names.empty?
|
|
84
|
+
|
|
85
|
+
@log.info "Executing on %d server(s): %s", server_names.size, server_names.join(", ")
|
|
86
|
+
@log.separator
|
|
87
|
+
|
|
88
|
+
results = {}
|
|
89
|
+
mutex = Mutex.new
|
|
90
|
+
threads = server_names.map do |name|
|
|
91
|
+
Thread.new do
|
|
92
|
+
begin
|
|
93
|
+
server = find_server(name)
|
|
94
|
+
ssh = External::Ssh.new(server.public_ipv4, @config.ssh_key_path)
|
|
95
|
+
|
|
96
|
+
@log.info "[%s] Executing...", name
|
|
97
|
+
output = ssh.execute(command)
|
|
98
|
+
|
|
99
|
+
output.strip.split("\n").each do |line|
|
|
100
|
+
puts "[#{name}] #{line}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
mutex.synchronize { results[name] = nil }
|
|
104
|
+
rescue StandardError => e
|
|
105
|
+
@log.error "[%s] Failed: %s", name, e.message
|
|
106
|
+
mutex.synchronize { results[name] = e }
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
threads.each(&:join)
|
|
112
|
+
|
|
113
|
+
@log.separator
|
|
114
|
+
|
|
115
|
+
failures = results.select { |_, err| err }.keys
|
|
116
|
+
if failures.any?
|
|
117
|
+
@log.warning "Command failed on %d server(s): %s", failures.size, failures.join(", ")
|
|
118
|
+
raise Errors::ServiceError, "command failed on some servers"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
@log.success "Command completed successfully on all servers"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def open_shell(server_name)
|
|
125
|
+
actual_server_name = resolve_server_name(server_name)
|
|
126
|
+
server = find_server(actual_server_name)
|
|
127
|
+
|
|
128
|
+
@log.info "Opening SSH shell to %s (%s)", actual_server_name, server.public_ipv4
|
|
129
|
+
|
|
130
|
+
ssh = External::Ssh.new(server.public_ipv4, @config.ssh_key_path)
|
|
131
|
+
ssh.open_shell
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def resolve_server_name(name)
|
|
135
|
+
return @config.server_name if name.nil? || name.empty? || name == "main"
|
|
136
|
+
|
|
137
|
+
parts = name.split("-")
|
|
138
|
+
if parts.length >= 2
|
|
139
|
+
num_str = parts.last
|
|
140
|
+
if num_str.match?(/^\d+$/)
|
|
141
|
+
group_name = parts[0...-1].join("-")
|
|
142
|
+
return @config.namer.server_name(group_name, num_str.to_i)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
@config.namer.server_name(name, 1)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def get_all_server_names
|
|
150
|
+
names = []
|
|
151
|
+
|
|
152
|
+
@config.deploy.application.servers.each do |group_name, group_config|
|
|
153
|
+
next unless group_config
|
|
154
|
+
|
|
155
|
+
count = group_config.count.positive? ? group_config.count : 1
|
|
156
|
+
(1..count).each do |i|
|
|
157
|
+
names << @config.namer.server_name(group_name, i)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
names
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def find_server(server_name)
|
|
165
|
+
server = @provider.find_server(server_name)
|
|
166
|
+
raise Errors::ServiceError, "server not found: #{server_name}" unless server
|
|
167
|
+
|
|
168
|
+
server
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nvoi
|
|
4
|
+
class Cli
|
|
5
|
+
module Logs
|
|
6
|
+
# Command streams logs from app pods
|
|
7
|
+
class Command
|
|
8
|
+
def initialize(options)
|
|
9
|
+
@options = options
|
|
10
|
+
@log = Nvoi.logger
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def run(app_name)
|
|
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
|
+
# Build deployment name from app name
|
|
28
|
+
deployment_name = @config.namer.app_deployment_name(app_name)
|
|
29
|
+
|
|
30
|
+
# Build kubectl logs command
|
|
31
|
+
# --prefix shows pod name, --all-containers handles multi-container pods
|
|
32
|
+
follow_flag = @options[:follow] ? "-f" : ""
|
|
33
|
+
tail_flag = "--tail=#{@options[:tail]}"
|
|
34
|
+
|
|
35
|
+
kubectl_cmd = "kubectl logs -l app=#{deployment_name} --prefix --all-containers #{follow_flag} #{tail_flag}".strip.squeeze(" ")
|
|
36
|
+
|
|
37
|
+
@log.info "Streaming logs for %s", deployment_name
|
|
38
|
+
|
|
39
|
+
ssh = External::Ssh.new(server.public_ipv4, @config.ssh_key_path)
|
|
40
|
+
ssh.execute(kubectl_cmd, stream: true)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def resolve_config_path
|
|
46
|
+
config_path = @options[:config] || "deploy.enc"
|
|
47
|
+
working_dir = @options[:dir]
|
|
48
|
+
|
|
49
|
+
if config_path == "deploy.enc" && working_dir && working_dir != "."
|
|
50
|
+
File.join(working_dir, "deploy.enc")
|
|
51
|
+
else
|
|
52
|
+
config_path
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def apply_branch_override
|
|
57
|
+
branch = @options[:branch]
|
|
58
|
+
return if branch.nil? || branch.empty?
|
|
59
|
+
|
|
60
|
+
override = Objects::ConfigOverride.new(branch:)
|
|
61
|
+
override.apply(@config)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|