|
12 | 12 | * |
13 | 13 | */ |
14 | 14 | #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> |
15 | 18 | #include <vix/cli/util/Ui.hpp> |
16 | 19 | #include <vix/utils/Env.hpp> |
17 | 20 |
|
@@ -71,6 +74,43 @@ namespace vix::commands |
71 | 74 | return s; |
72 | 75 | } |
73 | 76 |
|
| 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 | + |
74 | 114 | std::string shell_quote(const std::string &s) |
75 | 115 | { |
76 | 116 | std::string out = "'"; |
@@ -282,8 +322,114 @@ namespace vix::commands |
282 | 322 | int restartSec{3}; |
283 | 323 | int limitNoFile{65535}; |
284 | 324 | std::optional<fs::path> vixDir; |
| 325 | + std::optional<std::string> healthLocal; |
| 326 | + std::optional<std::string> healthPublic; |
| 327 | + std::uint64_t healthTimeoutMs{2000}; |
285 | 328 | }; |
286 | 329 |
|
| 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 | + |
287 | 433 | ServiceConfig load_service_config() |
288 | 434 | { |
289 | 435 | ServiceConfig cfg; |
@@ -317,6 +463,15 @@ namespace vix::commands |
317 | 463 | if (svc.contains("working_dir") && svc["working_dir"].is_string()) |
318 | 464 | cfg.workingDir = fs::path(svc["working_dir"].get<std::string>()); |
319 | 465 |
|
| 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 | + |
320 | 475 | if (svc.contains("exec") && svc["exec"].is_string()) |
321 | 476 | { |
322 | 477 | const fs::path exec = svc["exec"].get<std::string>(); |
@@ -503,6 +658,9 @@ namespace vix::commands |
503 | 658 | if (action == "install") |
504 | 659 | return install_service(); |
505 | 660 |
|
| 661 | + if (action == "health") |
| 662 | + return service_health(); |
| 663 | + |
506 | 664 | if (action == "start" || |
507 | 665 | action == "stop" || |
508 | 666 | action == "restart" || |
@@ -530,13 +688,14 @@ namespace vix::commands |
530 | 688 | << " stop Stop the service\n" |
531 | 689 | << " restart Restart the service\n" |
532 | 690 | << " 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" |
534 | 693 | << "Examples:\n" |
535 | 694 | << " vix service install\n" |
536 | 695 | << " vix service restart\n" |
537 | 696 | << " vix service status\n" |
538 | | - << " vix service logs\n"; |
539 | | - |
| 697 | + << " vix service logs\n" |
| 698 | + << " vix service health\n"; |
540 | 699 | return 0; |
541 | 700 | } |
542 | 701 | } |
0 commit comments