provisioning-core/nulib/primitives/io/service_check.nu

252 lines
9.8 KiB
Text
Raw Permalink Normal View History

use platform/target.nu [get-deployment-service-config get-enabled-services list-required-platform-services]
use platform/health.nu [check-service-health]
use platform/service_manager.nu [get-external-services is-port-listening]
def check-external-services-internal [external_config: record]: nothing -> list {
let db = ($external_config.database? | default {backend: "filesystem"})
let oci_registries = ($external_config.oci_registries? | default [])
let git_sources = ($external_config.git_sources? | default [])
mut results = []
if ($db.backend? | default "filesystem") == "filesystem" {
let path = ($db.path? | default "~/.provisioning/data")
let expanded_path = if ($path | str starts-with "~") {
$"($env.HOME)/($path | str substring 1..)"
} else {
$path
}
if ($expanded_path | path exists) {
$results = ($results | append {
service: "database", backend: $db.backend, status: "✓"
message: $"Filesystem storage available at ($expanded_path)"
})
} else {
$results = ($results | append {
service: "database", backend: $db.backend, status: "✗"
message: $"Path does not exist: ($expanded_path)"
})
}
}
$results
}
export def check-service-available [
service_url: string
service_name: string
]: nothing -> record {
let parsed = ($service_url | parse "http://{host}:{port}")
let port = if ($parsed | is-empty) { "unknown" } else { ($parsed | get port.0) }
let port_check = (do {^lsof -i :($port) -P -n | ^grep LISTEN} | complete)
let is_listening = ($port_check.exit_code == 0)
if $is_listening {
{available: true, port: $port, message: $"($service_name) is available on port ($port)"}
} else {
{available: false, port: $port, message: $"($service_name) is not available on port ($port)"}
}
}
export def check-external-services-status []: nothing -> list {
let external_services = (get-external-services)
if ($external_services | is-empty) { return [] }
$external_services | each {|svc|
let healthy = (is-port-listening $svc.port)
{
name: $svc.name,
srvc: ($svc.srvc? | default "external"),
url: $svc.url,
port: $svc.port,
required: ($svc.required? | default false),
healthy: $healthy,
dependencies: ($svc.dependencies? | default [])
}
}
}
# Transactional pre-operation health gate.
# Checks all enabled platform services and required external services.
# Fails hard (error make) if any required service is down — stops the operation before it starts.
# context: human-readable label for the error message (e.g. "component docker-mailserver update")
export def health-gate [context: string] {
let ext = (check-external-services-status)
# Gate only on platform services marked required=true in deployment config.
# Optional services (control-center, catalog-registry, etc.) don't block operations.
let failed_plat = (
list-required-platform-services
| where {|svc| not (try { check-service-health $svc.name } catch { false }) }
| each {|svc| {name: $svc.name} }
)
let failed_ext = ($ext | where {|s| $s.required and (not $s.healthy) })
if ($failed_ext | is-empty) and ($failed_plat | is-empty) { return }
mut lines: list<string> = [$"Operation blocked — required services are down: ($context)", ""]
if ($failed_plat | is-not-empty) {
$lines = ($lines | append " Platform services:")
for s in $failed_plat {
$lines = ($lines | append $" - ($s.name)")
}
$lines = ($lines | append "")
}
if ($failed_ext | is-not-empty) {
$lines = ($lines | append " External services (required=true):")
for s in $failed_ext {
$lines = ($lines | append $" - ($s.name) ($s.url)")
}
$lines = ($lines | append "")
}
$lines = ($lines | append " Check: prvng plat st && prvng plat ext")
$lines = ($lines | append " Start: prvng plat start")
error make --unspanned {msg: ($lines | str join "\n")}
}
export def check-platform-services-status []: nothing -> list {
let services = (get-enabled-services)
$services | each {|svc|
let healthy = (check-service-health $svc.name)
{name: $svc.name, status: (if $healthy {"healthy"} else {"unhealthy"}), priority: $svc.priority}
}
}
export def show-cascade-failure-report [failed_service: string]: nothing -> nothing {
print ""
print $"❌ ($failed_service) is not running."
print ""
print "Start all platform services:"
print " provisioning platform start"
print " prvng plat start # short alias"
print ""
print "Check service status:"
print " provisioning platform status"
print " prvng plat st # short alias"
print ""
}
export def verify-service-or-fail [
service_url: string
service_name: string
--check-command: string = ""
--check-alias: string = ""
--start-command: string = ""
--start-alias: string = ""
]: nothing -> record {
let check_result = (check-service-available $service_url $service_name)
if not $check_result.available {
print $"❌ ($service_name) not available at ($service_url)"
print ""
print $"Connection refused - ($service_name) is not running on port ($check_result.port)."
print ""
show-cascade-failure-report $service_name
if ($check_command | is-not-empty) {
print "To check service status:"
print $" ($check_command)"
if ($check_alias | is-not-empty) { print $" ($check_alias) # short alias" }
print ""
}
if ($start_command | is-not-empty) {
print "To start service:"
print $" ($start_command)"
if ($start_alias | is-not-empty) { print $" ($start_alias) # short alias" }
print ""
}
print $"Current endpoint: ($service_url)"
print "If using a custom endpoint, verify it with: --orchestrator <url>"
return {status: "error", message: $"($service_name) not available"}
}
{status: "ok", message: $"($service_name) is available"}
}
export def is-service-available [service_url: string, service_name: string]: nothing -> bool {
(check-service-available $service_url $service_name).available
}
export def check-daemon-availability []: nothing -> record {
let daemon_config = (get-deployment-service-config "provisioning_daemon")
let daemon_port = ($daemon_config.server?.port? | default 9014)
let port_check = (do {^lsof -i :($daemon_port) -P -n | ^grep LISTEN} | complete)
{available: ($port_check.exit_code == 0), port: $daemon_port}
}
export def check-nu-daemon-availability []: nothing -> record {
let daemon_config = (get-deployment-service-config "nu_daemon")
let daemon_port = ($daemon_config.server?.port? | default 9095)
let port_check = (do {^lsof -i :($daemon_port) -P -n | ^grep LISTEN} | complete)
{available: ($port_check.exit_code == 0), port: $daemon_port}
}
export def verify-daemon-or-block [operation: string]: nothing -> record {
let daemon_check = (check-daemon-availability)
if not $daemon_check.available {
print ""
print "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print "❌ CRITICAL: provisioning_daemon not available"
print "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print ""
print $"The provisioning daemon is required for operation: ($operation)"
print $"Daemon is not listening on port ($daemon_check.port)"
print ""
print "The daemon is a CRITICAL component - all operations require it."
print ""
print "To check daemon status:"
print " provisioning platform status"
print " prvng plat st # short alias"
print ""
print "To start the daemon:"
print " provisioning platform start provisioning_daemon"
print " prvng plat start provisioning_daemon # short alias"
print ""
print "Allowed operations without daemon:"
print " • help / -h / --help - View help"
print " • platform <cmd> - Manage platform services"
print " • setup - Initial setup"
print ""
return {status: "error", message: "provisioning_daemon not available"}
}
{status: "ok", message: "provisioning_daemon is available"}
}
export def verify-nu-daemon-or-block [operation: string]: nothing -> record {
let daemon_check = (check-nu-daemon-availability)
if not $daemon_check.available {
print ""
print "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print "❌ CRITICAL: nu_daemon not available"
print "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print ""
print $"The nu_daemon is required for operation: ($operation)"
print $"nu_daemon is not listening on port ($daemon_check.port)"
print ""
print "To check status:"
print " prvng plat st"
print ""
print "To start:"
print " prvng plat start nu_daemon"
print ""
return {status: "error", message: "nu_daemon not available"}
}
{status: "ok", message: "nu_daemon is available"}
}