#!/usr/bin/env nu # Pre-flight Check System # Validates service prerequisites before operations use manager.nu [load-service-registry is-service-running get-service-definition] use dependencies.nu [resolve-dependencies get-startup-order] # Check required services for operation export def check-required-services [ operation: string ]: nothing -> record { let registry = (load-service-registry) # Find all services required for this operation let required_services = ( $registry | transpose name config | where { |row| $operation in $row.config.required_for } | get name ) if ($required_services | is-empty) { return { operation: $operation required_services: [] all_running: true missing_services: [] can_auto_start: true message: "No services required for this operation" } } # Check which services are running def partition-services [services: list, running: list, missing: list]: nothing -> record { if ($services | is-empty) { return { running: $running, missing: $missing } } let first = ($services | get 0) let rest = ($services | skip 1) if (is-service-running $first) { partition-services $rest ($running | append $first) $missing } else { partition-services $rest $running ($missing | append $first) } } let service_status = (partition-services $required_services [] []) let running = $service_status.running let missing = $service_status.missing # Check if missing services can be auto-started let can_auto_start = ( $missing | all { |service| let service_def = (get-service-definition $service) $service_def.startup.auto_start } ) { operation: $operation required_services: $required_services running_services: $running missing_services: $missing all_running: ($missing | is-empty) can_auto_start: $can_auto_start message: (if ($missing | is-empty) { "All required services are running" } else if $can_auto_start { $"Services need to be started: ($missing | str join ', ')" } else { $"Required services not running and cannot auto-start: ($missing | str join ', ')" }) } } # Validate service prerequisites export def validate-service-prerequisites [ service_name: string ]: nothing -> record { let service_def = (get-service-definition $service_name) # Check deployment mode requirements let issues = ( if $service_def.deployment.mode == "docker" or $service_def.deployment.mode == "docker-compose" { # Check if Docker is available let docker_check = (do { docker --version } | complete) if $docker_check.exit_code != 0 { ["Docker is not installed or not running"] } else { [] } } else if $service_def.deployment.mode == "kubernetes" { # Check if kubectl is available let kubectl_check = (do { kubectl version --client } | complete) if $kubectl_check.exit_code != 0 { ["kubectl is not installed"] } else { [] } } else if $service_def.deployment.mode == "binary" { # Check if binary exists let binary_path = ($service_def.deployment.binary.binary_path | str replace -a '${HOME}' $env.HOME) if not ($binary_path | path exists) { [$"Binary not found: ($binary_path)"] } else { [] } } else { [] } ) # Check dependencies def check-deps [deps: list, warnings: list]: nothing -> list { if ($deps | is-empty) { return $warnings } let first = ($deps | get 0) let rest = ($deps | skip 1) let new_warnings = if (is-service-running $first) { $warnings } else { $warnings | append $"Dependency '($first)' is not running" } check-deps $rest $new_warnings } let warnings = (check-deps $service_def.dependencies []) # Check conflicts def check-conflicts [conflicts: list, issues: list]: nothing -> list { if ($conflicts | is-empty) { return $issues } let first = ($conflicts | get 0) let rest = ($conflicts | skip 1) let new_issues = if (is-service-running $first) { $issues | append $"Conflicting service '($first)' is running" } else { $issues } check-conflicts $rest $new_issues } let all_issues = (check-conflicts $service_def.conflicts $issues) { service: $service_name valid: ($all_issues | is-empty) issues: $all_issues warnings: $warnings can_start: ($all_issues | is-empty) message: (if ($all_issues | is-empty) { "All prerequisites met" } else { $"Prerequisites not met: ($all_issues | str join '; ')" }) } } # Auto-start required services export def auto-start-required-services [ operation: string ]: nothing -> record { let check = (check-required-services $operation) if $check.all_running { return { success: true message: "All required services already running" started_services: [] } } if not $check.can_auto_start { return { success: false message: "Some required services cannot be auto-started" failed_services: $check.missing_services } } # Get startup order for missing services let startup_order = (get-startup-order $check.missing_services) print $"Starting required services in order: ($startup_order | str join ' -> ')" # Helper to start services in sequence def start-services-seq [services: list, started: list, failed: list]: nothing -> record { if ($services | is-empty) { return { started: $started, failed: $failed } } let first = ($services | get 0) let rest = ($services | skip 1) print $"Starting [$first]..." use manager.nu start-service let start_result = (do { start-service $first } | complete) if $start_result.exit_code == 0 and ($start_result.stdout == "true" or $start_result.stdout == true) { print $"✅ [$first] started successfully" start-services-seq $rest ($started | append $first) $failed } else { print $"❌ Failed to start [$first]: ($start_result.stderr)" { started: $started, failed: ($failed | append $first) } } } let result = (start-services-seq $startup_order [] []) let started = $result.started let failed = $result.failed { success: ($failed | is-empty) message: (if ($failed | is-empty) { $"Successfully started ($started | length) services" } else { $"Failed to start some services: ($failed | str join ', ')" }) started_services: $started failed_services: $failed } } # Check service conflicts export def check-service-conflicts [ service_name: string ]: nothing -> record { let service_def = (get-service-definition $service_name) # Helper to check conflicts def find-conflicts [conflicts: list, result: list]: nothing -> list { if ($conflicts | is-empty) { return $result } let first = ($conflicts | get 0) let rest = ($conflicts | skip 1) let new_result = if (is-service-running $first) { $result | append { service: $first status: "running" action_required: "stop" } } else { $result } find-conflicts $rest $new_result } let conflicts = (find-conflicts $service_def.conflicts []) { service: $service_name has_conflicts: (not ($conflicts | is-empty)) conflicts: $conflicts can_start: ($conflicts | is-empty) message: (if ($conflicts | is-empty) { "No conflicting services running" } else { $"Conflicting services running: (($conflicts | get service) | str join ', ')" }) } } # Validate all services export def validate-all-services []: nothing -> record { let registry = (load-service-registry) let validation_results = ( $registry | transpose name config | each { |row| validate-service-prerequisites $row.name } ) let total = ($validation_results | length) let valid = ($validation_results | where valid | length) let invalid = ($validation_results | where { |r| not $r.valid } | length) { total_services: $total valid_services: $valid invalid_services: $invalid all_valid: ($invalid == 0) results: $validation_results message: $"($valid)/($total) services have valid configurations" } } # Pre-flight check for service start export def preflight-start-service [ service_name: string ]: nothing -> record { print $"Running pre-flight checks for ($service_name)..." # 1. Validate prerequisites let prereq_check = (validate-service-prerequisites $service_name) if not $prereq_check.valid { return { can_start: false reason: "prerequisites" details: $prereq_check } } # 2. Check conflicts let conflict_check = (check-service-conflicts $service_name) if $conflict_check.has_conflicts { return { can_start: false reason: "conflicts" details: $conflict_check } } # 3. Check dependencies let service_def = (get-service-definition $service_name) # Helper to collect missing dependencies def collect-missing-deps [deps: list, missing: list]: nothing -> list { if ($deps | is-empty) { return $missing } let first = ($deps | get 0) let rest = ($deps | skip 1) let new_missing = if (is-service-running $first) { $missing } else { let dep_def = (get-service-definition $first) $missing | append { service: $first auto_start: $dep_def.startup.auto_start } } collect-missing-deps $rest $new_missing } let missing_deps = (collect-missing-deps $service_def.dependencies []) if not ($missing_deps | is-empty) { let can_auto_start = ($missing_deps | all { |d| $d.auto_start }) return { can_start: $can_auto_start reason: "dependencies" details: { missing_dependencies: $missing_deps can_auto_start: $can_auto_start } } } # All checks passed { can_start: true reason: "all_checks_passed" details: { message: "Pre-flight checks passed" } } } # Get service readiness report export def get-readiness-report []: nothing -> record { let registry = (load-service-registry) let services = ( $registry | transpose name config | each { |row| let is_running = (is-service-running $row.name) let prereq_check = (validate-service-prerequisites $row.name) { name: $row.name type: $row.config.type category: $row.config.category running: $is_running auto_start: $row.config.startup.auto_start valid_config: $prereq_check.valid can_start: $prereq_check.can_start issues: ($prereq_check.issues | length) } } ) let total = ($services | length) let running = ($services | where running | length) let ready = ($services | where can_start | length) { total_services: $total running_services: $running ready_to_start: $ready services: $services summary: $"($running) running, ($ready) ready to start out of ($total) total" } }