Skip to content

Commit 137fc9e

Browse files
committed
feat(cli): add service health and proxy deploy checks
2 parents d1528a9 + e3fe592 commit 137fc9e

6 files changed

Lines changed: 216 additions & 19 deletions

File tree

include/vix/cli/commands/deploy/DeployTypes.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,16 @@ namespace vix::commands::deploy
9595
*/
9696
bool healthPublic{false};
9797

98+
/**
99+
* @brief Whether deploy should validate the Nginx reverse proxy config.
100+
*/
101+
bool proxyCheck{false};
102+
103+
/**
104+
* @brief Whether deploy should reload Nginx after validation.
105+
*/
106+
bool proxyReload{false};
107+
98108
/**
99109
* @brief Whether deploy should print recent service logs on failure.
100110
*/

src/commands/DeployCommand.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ namespace vix::commands
122122
<< " production.deploy.service\n"
123123
<< " production.deploy.health_local\n"
124124
<< " production.deploy.health_public\n"
125+
<< " production.deploy.proxy_check\n"
126+
<< " production.deploy.proxy_reload\n"
125127
<< " production.deploy.logs_on_failure\n";
126128

127129
return 0;

src/commands/ServiceCommand.cpp

Lines changed: 162 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
*
1313
*/
1414
#include <vix/cli/commands/ServiceCommand.hpp>
15+
#include <vix/net/http/CurlClient.hpp>
16+
#include <vix/net/http/ClientRequest.hpp>
17+
#include <vix/net/http/Method.hpp>
1518
#include <vix/cli/util/Ui.hpp>
1619
#include <vix/utils/Env.hpp>
1720

@@ -71,6 +74,43 @@ namespace vix::commands
7174
return s;
7275
}
7376

77+
struct HealthResult
78+
{
79+
bool ok{false};
80+
int statusCode{0};
81+
std::string error;
82+
};
83+
84+
HealthResult run_http_health_check(
85+
const std::string &url,
86+
std::uint64_t timeoutMs)
87+
{
88+
HealthResult result;
89+
90+
vix::net::http::CurlClient client;
91+
92+
vix::net::http::ClientRequest request;
93+
request.set_method(vix::net::http::Method::Get)
94+
.set_url(url)
95+
.set_timeout_ms(timeoutMs);
96+
97+
auto response = client.send(request);
98+
99+
if (!response)
100+
{
101+
result.error = std::string(response.error().message());
102+
return result;
103+
}
104+
105+
result.statusCode = response.value().status_code;
106+
result.ok = response.value().success();
107+
108+
if (!response.value().error.empty())
109+
result.error = response.value().error;
110+
111+
return result;
112+
}
113+
74114
std::string shell_quote(const std::string &s)
75115
{
76116
std::string out = "'";
@@ -282,8 +322,114 @@ namespace vix::commands
282322
int restartSec{3};
283323
int limitNoFile{65535};
284324
std::optional<fs::path> vixDir;
325+
std::optional<std::string> healthLocal;
326+
std::optional<std::string> healthPublic;
327+
std::uint64_t healthTimeoutMs{2000};
285328
};
286329

330+
ServiceConfig load_service_config();
331+
332+
int service_health()
333+
{
334+
const ServiceConfig cfg = load_service_config();
335+
336+
vix::cli::util::section(std::cout, "Service Health");
337+
338+
vix::cli::util::kv(std::cout, "Service", cfg.serviceName);
339+
vix::cli::util::kv(std::cout, "Timeout", std::to_string(cfg.healthTimeoutMs) + "ms");
340+
341+
bool ok = true;
342+
343+
if (cfg.healthLocal)
344+
{
345+
vix::cli::util::kv(std::cout, "Health local", *cfg.healthLocal);
346+
347+
const HealthResult result =
348+
run_http_health_check(*cfg.healthLocal, cfg.healthTimeoutMs);
349+
350+
if (result.ok)
351+
{
352+
vix::cli::util::ok_line(
353+
std::cout,
354+
"local health check passed: HTTP " + std::to_string(result.statusCode));
355+
}
356+
else
357+
{
358+
ok = false;
359+
360+
vix::cli::util::err_line(
361+
std::cerr,
362+
"local health check failed");
363+
364+
if (result.statusCode > 0)
365+
{
366+
vix::cli::util::warn_line(
367+
std::cerr,
368+
"HTTP status: " + std::to_string(result.statusCode));
369+
}
370+
371+
if (!result.error.empty())
372+
{
373+
vix::cli::util::warn_line(
374+
std::cerr,
375+
result.error);
376+
}
377+
}
378+
}
379+
380+
if (cfg.healthPublic)
381+
{
382+
vix::cli::util::kv(std::cout, "Health public", *cfg.healthPublic);
383+
384+
const HealthResult result =
385+
run_http_health_check(*cfg.healthPublic, cfg.healthTimeoutMs);
386+
387+
if (result.ok)
388+
{
389+
vix::cli::util::ok_line(
390+
std::cout,
391+
"public health check passed: HTTP " + std::to_string(result.statusCode));
392+
}
393+
else
394+
{
395+
ok = false;
396+
397+
vix::cli::util::err_line(
398+
std::cerr,
399+
"public health check failed");
400+
401+
if (result.statusCode > 0)
402+
{
403+
vix::cli::util::warn_line(
404+
std::cerr,
405+
"HTTP status: " + std::to_string(result.statusCode));
406+
}
407+
408+
if (!result.error.empty())
409+
{
410+
vix::cli::util::warn_line(
411+
std::cerr,
412+
result.error);
413+
}
414+
}
415+
}
416+
417+
if (!cfg.healthLocal && !cfg.healthPublic)
418+
{
419+
vix::cli::util::warn_line(
420+
std::cerr,
421+
"No health check configured.");
422+
423+
vix::cli::util::warn_line(
424+
std::cerr,
425+
"Add production.service.health_local or production.service.health_public to vix.json.");
426+
427+
return 1;
428+
}
429+
430+
return ok ? 0 : 1;
431+
}
432+
287433
ServiceConfig load_service_config()
288434
{
289435
ServiceConfig cfg;
@@ -317,6 +463,15 @@ namespace vix::commands
317463
if (svc.contains("working_dir") && svc["working_dir"].is_string())
318464
cfg.workingDir = fs::path(svc["working_dir"].get<std::string>());
319465

466+
if (svc.contains("health_local") && svc["health_local"].is_string())
467+
cfg.healthLocal = svc["health_local"].get<std::string>();
468+
469+
if (svc.contains("health_public") && svc["health_public"].is_string())
470+
cfg.healthPublic = svc["health_public"].get<std::string>();
471+
472+
if (svc.contains("health_timeout_ms") && svc["health_timeout_ms"].is_number_unsigned())
473+
cfg.healthTimeoutMs = svc["health_timeout_ms"].get<std::uint64_t>();
474+
320475
if (svc.contains("exec") && svc["exec"].is_string())
321476
{
322477
const fs::path exec = svc["exec"].get<std::string>();
@@ -503,6 +658,9 @@ namespace vix::commands
503658
if (action == "install")
504659
return install_service();
505660

661+
if (action == "health")
662+
return service_health();
663+
506664
if (action == "start" ||
507665
action == "stop" ||
508666
action == "restart" ||
@@ -530,13 +688,14 @@ namespace vix::commands
530688
<< " stop Stop the service\n"
531689
<< " restart Restart the service\n"
532690
<< " status Show service status\n"
533-
<< " logs Show recent service logs\n\n"
691+
<< " logs Show recent service logs\n"
692+
<< " health Run configured HTTP health checks\n\n"
534693
<< "Examples:\n"
535694
<< " vix service install\n"
536695
<< " vix service restart\n"
537696
<< " vix service status\n"
538-
<< " vix service logs\n";
539-
697+
<< " vix service logs\n"
698+
<< " vix service health\n";
540699
return 0;
541700
}
542701
}

src/commands/deploy/DeployConfig.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,12 @@ namespace vix::commands::deploy
214214
if (auto healthPublic = read_bool(deploy, "health_public"))
215215
cfg.healthPublic = *healthPublic;
216216

217+
if (auto proxyCheck = read_bool(deploy, "proxy_check"))
218+
cfg.proxyCheck = *proxyCheck;
219+
220+
if (auto proxyReload = read_bool(deploy, "proxy_reload"))
221+
cfg.proxyReload = *proxyReload;
222+
217223
if (auto logs = read_bool(deploy, "logs_on_failure"))
218224
cfg.logsOnFailure = *logs;
219225

src/commands/deploy/DeployOutput.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ namespace vix::commands::deploy::output
4646
vix::cli::util::kv(out, "Service", cfg.serviceName.empty() ? "(missing)" : cfg.serviceName);
4747
vix::cli::util::kv(out, "Health local", enabled_disabled(cfg.healthLocal));
4848
vix::cli::util::kv(out, "Health public", enabled_disabled(cfg.healthPublic));
49+
vix::cli::util::kv(out, "Proxy check", enabled_disabled(cfg.proxyCheck));
50+
vix::cli::util::kv(out, "Proxy reload", enabled_disabled(cfg.proxyReload));
4951
vix::cli::util::kv(out, "Logs on failure", enabled_disabled(cfg.logsOnFailure));
5052
vix::cli::util::kv(out, "Dry run", yes_no(options.dryRun));
5153
vix::cli::util::kv(out, "Verbose", yes_no(options.verbose));

src/commands/deploy/DeployRunner.cpp

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -147,28 +147,40 @@ namespace vix::commands::deploy::runner
147147
return run_cmd(cmd, options);
148148
}
149149

150-
bool health_local(
150+
bool service_health(
151151
const DeployConfig &cfg,
152152
const DeployOptions &options)
153153
{
154-
if (!cfg.healthLocal)
154+
if (!cfg.healthLocal && !cfg.healthPublic)
155155
return true;
156156

157-
output::step(std::cout, "Local Health");
157+
output::step(std::cout, "Service Health");
158158

159-
return run_cmd("vix health local", options);
159+
return run_cmd("vix service health", options);
160160
}
161161

162-
bool health_public(
162+
bool proxy_check(
163163
const DeployConfig &cfg,
164164
const DeployOptions &options)
165165
{
166-
if (!cfg.healthPublic)
166+
if (!cfg.proxyCheck && !cfg.proxyReload)
167167
return true;
168168

169-
output::step(std::cout, "Public Health");
169+
output::step(std::cout, "Proxy Check");
170170

171-
return run_cmd("vix health public", options);
171+
return run_cmd("vix proxy nginx check", options);
172+
}
173+
174+
bool proxy_reload(
175+
const DeployConfig &cfg,
176+
const DeployOptions &options)
177+
{
178+
if (!cfg.proxyReload)
179+
return true;
180+
181+
output::step(std::cout, "Proxy Reload");
182+
183+
return run_cmd("vix proxy nginx reload", options);
172184
}
173185
}
174186

@@ -212,17 +224,23 @@ namespace vix::commands::deploy::runner
212224

213225
output::ok(std::cout, "service is active");
214226

215-
if (!health_local(cfg, options))
216-
return fail(cfg, options, "local health check failed", "run `vix health local`");
227+
if (!service_health(cfg, options))
228+
return fail(cfg, options, "service health check failed", "run `vix service health`");
229+
230+
if (cfg.healthLocal || cfg.healthPublic)
231+
output::ok(std::cout, "service health checks passed");
232+
233+
if (!proxy_check(cfg, options))
234+
return fail(cfg, options, "proxy check failed", "run `vix proxy nginx check`");
217235

218-
if (cfg.healthLocal)
219-
output::ok(std::cout, "local endpoint is healthy");
236+
if (cfg.proxyCheck || cfg.proxyReload)
237+
output::ok(std::cout, "proxy config is valid");
220238

221-
if (!health_public(cfg, options))
222-
return fail(cfg, options, "public health check failed", "run `vix health public`");
239+
if (!proxy_reload(cfg, options))
240+
return fail(cfg, options, "proxy reload failed", "run `vix proxy nginx reload`");
223241

224-
if (cfg.healthPublic)
225-
output::ok(std::cout, "public endpoint is healthy");
242+
if (cfg.proxyReload)
243+
output::ok(std::cout, "proxy reloaded");
226244

227245
output::ok(std::cout, "deployment completed");
228246

0 commit comments

Comments
 (0)