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.
Files changed (156) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/todo/refactor/00-overview.md +171 -0
  3. data/.claude/todo/refactor/01-objects.md +96 -0
  4. data/.claude/todo/refactor/02-utils.md +143 -0
  5. data/.claude/todo/refactor/03-external-cloud.md +164 -0
  6. data/.claude/todo/refactor/04-external-dns.md +104 -0
  7. data/.claude/todo/refactor/05-external.md +133 -0
  8. data/.claude/todo/refactor/06-cli.md +123 -0
  9. data/.claude/todo/refactor/07-cli-deploy-command.md +177 -0
  10. data/.claude/todo/refactor/08-cli-deploy-steps.md +201 -0
  11. data/.claude/todo/refactor/09-cli-delete-command.md +169 -0
  12. data/.claude/todo/refactor/10-cli-exec-command.md +157 -0
  13. data/.claude/todo/refactor/11-cli-credentials-command.md +190 -0
  14. data/.claude/todo/refactor/12-cli-db-command.md +128 -0
  15. data/.claude/todo/refactor/_target.md +79 -0
  16. data/.claude/todo/refactor-execution/00-entrypoint.md +49 -0
  17. data/.claude/todo/refactor-execution/01-objects.md +42 -0
  18. data/.claude/todo/refactor-execution/02-utils.md +41 -0
  19. data/.claude/todo/refactor-execution/03-external-cloud.md +38 -0
  20. data/.claude/todo/refactor-execution/04-external-dns.md +35 -0
  21. data/.claude/todo/refactor-execution/05-external-other.md +46 -0
  22. data/.claude/todo/refactor-execution/06-cli-deploy.md +45 -0
  23. data/.claude/todo/refactor-execution/07-cli-delete.md +43 -0
  24. data/.claude/todo/refactor-execution/08-cli-exec.md +30 -0
  25. data/.claude/todo/refactor-execution/09-cli-credentials.md +34 -0
  26. data/.claude/todo/refactor-execution/10-cli-db.md +31 -0
  27. data/.claude/todo/refactor-execution/11-cli-router.md +44 -0
  28. data/.claude/todo/refactor-execution/12-cleanup.md +120 -0
  29. data/.claude/todo/refactor-execution/_monitoring-strategy.md +126 -0
  30. data/.claude/todo/scaleway.impl.md +644 -0
  31. data/.claude/todo/scaleway.reference.md +520 -0
  32. data/.claude/todos.md +550 -0
  33. data/Gemfile +6 -0
  34. data/Gemfile.lock +46 -5
  35. data/Rakefile +1 -1
  36. data/doc/config-schema.yaml +44 -11
  37. data/examples/golang/deploy.enc +0 -0
  38. data/examples/golang/main.go +18 -0
  39. data/exe/nvoi +3 -1
  40. data/ingest +0 -0
  41. data/lib/nvoi/cli/config/command.rb +219 -0
  42. data/lib/nvoi/cli/credentials/edit/command.rb +384 -0
  43. data/lib/nvoi/cli/credentials/show/command.rb +35 -0
  44. data/lib/nvoi/cli/db/command.rb +308 -0
  45. data/lib/nvoi/cli/delete/command.rb +75 -0
  46. data/lib/nvoi/cli/delete/steps/detach_volumes.rb +98 -0
  47. data/lib/nvoi/cli/delete/steps/teardown_dns.rb +50 -0
  48. data/lib/nvoi/cli/delete/steps/teardown_firewall.rb +46 -0
  49. data/lib/nvoi/cli/delete/steps/teardown_network.rb +30 -0
  50. data/lib/nvoi/cli/delete/steps/teardown_server.rb +50 -0
  51. data/lib/nvoi/cli/delete/steps/teardown_tunnel.rb +44 -0
  52. data/lib/nvoi/cli/delete/steps/teardown_volume.rb +61 -0
  53. data/lib/nvoi/cli/deploy/command.rb +184 -0
  54. data/lib/nvoi/cli/deploy/steps/build_image.rb +27 -0
  55. data/lib/nvoi/cli/deploy/steps/cleanup_images.rb +42 -0
  56. data/lib/nvoi/cli/deploy/steps/configure_tunnel.rb +102 -0
  57. data/lib/nvoi/cli/deploy/steps/deploy_service.rb +399 -0
  58. data/lib/nvoi/cli/deploy/steps/provision_network.rb +44 -0
  59. data/lib/nvoi/cli/deploy/steps/provision_server.rb +143 -0
  60. data/lib/nvoi/cli/deploy/steps/provision_volume.rb +171 -0
  61. data/lib/nvoi/cli/deploy/steps/setup_k3s.rb +490 -0
  62. data/lib/nvoi/cli/exec/command.rb +173 -0
  63. data/lib/nvoi/cli/logs/command.rb +66 -0
  64. data/lib/nvoi/cli/onboard/command.rb +761 -0
  65. data/lib/nvoi/cli/unlock/command.rb +72 -0
  66. data/lib/nvoi/cli.rb +339 -141
  67. data/lib/nvoi/config_api/actions/app.rb +53 -0
  68. data/lib/nvoi/config_api/actions/compute_provider.rb +55 -0
  69. data/lib/nvoi/config_api/actions/database.rb +70 -0
  70. data/lib/nvoi/config_api/actions/domain_provider.rb +40 -0
  71. data/lib/nvoi/config_api/actions/env.rb +32 -0
  72. data/lib/nvoi/config_api/actions/init.rb +67 -0
  73. data/lib/nvoi/config_api/actions/secret.rb +32 -0
  74. data/lib/nvoi/config_api/actions/server.rb +66 -0
  75. data/lib/nvoi/config_api/actions/service.rb +52 -0
  76. data/lib/nvoi/config_api/actions/volume.rb +40 -0
  77. data/lib/nvoi/config_api/base.rb +38 -0
  78. data/lib/nvoi/config_api/result.rb +26 -0
  79. data/lib/nvoi/config_api.rb +93 -0
  80. data/lib/nvoi/errors.rb +68 -50
  81. data/lib/nvoi/external/cloud/aws.rb +450 -0
  82. data/lib/nvoi/external/cloud/base.rb +99 -0
  83. data/lib/nvoi/external/cloud/factory.rb +48 -0
  84. data/lib/nvoi/external/cloud/hetzner.rb +402 -0
  85. data/lib/nvoi/external/cloud/scaleway.rb +559 -0
  86. data/lib/nvoi/external/cloud.rb +15 -0
  87. data/lib/nvoi/external/containerd.rb +86 -0
  88. data/lib/nvoi/external/database/mysql.rb +84 -0
  89. data/lib/nvoi/external/database/postgres.rb +82 -0
  90. data/lib/nvoi/external/database/provider.rb +65 -0
  91. data/lib/nvoi/external/database/sqlite.rb +72 -0
  92. data/lib/nvoi/external/database.rb +22 -0
  93. data/lib/nvoi/external/dns/cloudflare.rb +310 -0
  94. data/lib/nvoi/external/kubectl.rb +65 -0
  95. data/lib/nvoi/external/ssh.rb +106 -0
  96. data/lib/nvoi/objects/config_override.rb +60 -0
  97. data/lib/nvoi/objects/configuration.rb +483 -0
  98. data/lib/nvoi/objects/database.rb +56 -0
  99. data/lib/nvoi/objects/dns.rb +14 -0
  100. data/lib/nvoi/objects/firewall.rb +11 -0
  101. data/lib/nvoi/objects/network.rb +11 -0
  102. data/lib/nvoi/objects/server.rb +14 -0
  103. data/lib/nvoi/objects/service_spec.rb +26 -0
  104. data/lib/nvoi/objects/tunnel.rb +14 -0
  105. data/lib/nvoi/objects/volume.rb +17 -0
  106. data/lib/nvoi/utils/config_loader.rb +172 -0
  107. data/lib/nvoi/utils/constants.rb +61 -0
  108. data/lib/nvoi/{credentials/manager.rb → utils/credential_store.rb} +16 -16
  109. data/lib/nvoi/{credentials → utils}/crypto.rb +8 -5
  110. data/lib/nvoi/{config → utils}/env_resolver.rb +10 -2
  111. data/lib/nvoi/utils/logger.rb +84 -0
  112. data/lib/nvoi/{config/naming.rb → utils/namer.rb} +37 -25
  113. data/lib/nvoi/{deployer → utils}/retry.rb +23 -3
  114. data/lib/nvoi/utils/templates.rb +62 -0
  115. data/lib/nvoi/version.rb +1 -1
  116. data/lib/nvoi.rb +27 -55
  117. data/templates/app-ingress.yaml.erb +3 -1
  118. data/templates/error-backend.yaml.erb +134 -0
  119. metadata +121 -44
  120. data/examples/golang/deploy.yml +0 -54
  121. data/lib/nvoi/cloudflare/client.rb +0 -287
  122. data/lib/nvoi/config/config.rb +0 -248
  123. data/lib/nvoi/config/loader.rb +0 -102
  124. data/lib/nvoi/config/ssh_keys.rb +0 -82
  125. data/lib/nvoi/config/types.rb +0 -274
  126. data/lib/nvoi/constants.rb +0 -59
  127. data/lib/nvoi/credentials/editor.rb +0 -272
  128. data/lib/nvoi/deployer/cleaner.rb +0 -36
  129. data/lib/nvoi/deployer/image_builder.rb +0 -23
  130. data/lib/nvoi/deployer/infrastructure.rb +0 -126
  131. data/lib/nvoi/deployer/orchestrator.rb +0 -146
  132. data/lib/nvoi/deployer/service_deployer.rb +0 -311
  133. data/lib/nvoi/deployer/tunnel_manager.rb +0 -57
  134. data/lib/nvoi/deployer/types.rb +0 -8
  135. data/lib/nvoi/k8s/renderer.rb +0 -44
  136. data/lib/nvoi/k8s/templates.rb +0 -29
  137. data/lib/nvoi/logger.rb +0 -72
  138. data/lib/nvoi/providers/aws.rb +0 -403
  139. data/lib/nvoi/providers/base.rb +0 -111
  140. data/lib/nvoi/providers/hetzner.rb +0 -288
  141. data/lib/nvoi/providers/hetzner_client.rb +0 -170
  142. data/lib/nvoi/remote/docker_manager.rb +0 -203
  143. data/lib/nvoi/remote/ssh_executor.rb +0 -72
  144. data/lib/nvoi/remote/volume_manager.rb +0 -103
  145. data/lib/nvoi/service/delete.rb +0 -234
  146. data/lib/nvoi/service/deploy.rb +0 -80
  147. data/lib/nvoi/service/exec.rb +0 -144
  148. data/lib/nvoi/service/provider.rb +0 -36
  149. data/lib/nvoi/steps/application_deployer.rb +0 -26
  150. data/lib/nvoi/steps/database_provisioner.rb +0 -60
  151. data/lib/nvoi/steps/k3s_cluster_setup.rb +0 -105
  152. data/lib/nvoi/steps/k3s_provisioner.rb +0 -351
  153. data/lib/nvoi/steps/server_provisioner.rb +0 -43
  154. data/lib/nvoi/steps/services_provisioner.rb +0 -29
  155. data/lib/nvoi/steps/tunnel_configurator.rb +0 -66
  156. 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