prvng_core/nulib/taskservs/check_mode.nu
Jesús Pérez 894046ef5a
feat(core): three-layer DAG, unified component arch, commands-registry cache, Nushell 0.112.2 migration
- DAG architecture: `dag show/validate/export` (nulib/main_provisioning/dag.nu),
    config loader (lib_provisioning/config/loader/dag.nu), taskserv dag-executor.
    Backed by schemas/lib/dag/*.ncl; orchestrator emits NATS events via
    WorkspaceComposition::into_workflow. See ADR-020, ADR-021.
  - Unified Component Architecture: components/mod.nu, main_provisioning/
    {components,workflow,extensions,ontoref-queries}.nu. Full workflow engine with
    topological sort and NATS subject emission. Blocks A-H complete (libre-daoshi).
  - Commands-registry: nulib/commands-registry.ncl (Nickel source, 314 lines) +
    JSON cache at ~/.cache/provisioning/commands-registry.json rebuilt on source
    change. cli/provisioning fast-path alias expansion avoids cold Nu startup.
    ADDING_COMMANDS.md documents new-command workflow.
  - Platform service manager: service-manager.nu (+573), startup.nu (+611),
    service-check.nu (+255); autostart/bootstrap/health/target refactored.
  - Nushell 0.112.2 migration: removed all try/catch and bash redirections;
    external commands prefixed with ^; type signatures enforced. Driven by
    scripts/refactor-try-catch{,-simplified}.nu.
  - TTY stack: removed shlib/*-tty.sh; replaced by cli/tty-dispatch.sh,
    tty-filter.sh, tty-commands.conf.
  - New domain modules: images/ (golden image lifecycle), workspace/{state,sync}.nu,
    main_provisioning/{bootstrap,cluster-deploy,fip,state}.nu, commands/{state,
    build,integrations/auth,utilities/alias}.nu, platform.nu expanded (+874).
  - Config loader overhaul: loader/core.nu slimmed (-759), cache/core.nu
    refactored (-454), removed legacy loaders/file_loader.nu (-330).
  - Thirteen new provisioning-<domain>.nu top-level modules for bash dispatcher.
  - Tests: test_workspace_state.nu (+351); updates to test_oci_registry,
    test_services.
  - README + CHANGELOG updated.
2026-04-17 04:27:33 +01:00

373 lines
12 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Enhanced Check Mode for Taskservs
# Provides dry-run capabilities with detailed validation and preview
# REMOVED: use lib_provisioning * - causes circular import
use utils.nu *
use deps_validator.nu *
use validate.nu *
use ../lib_provisioning/config/accessor.nu *
use ../lib_provisioning/utils/ssh.nu [scp_to, ssh_cmd]
# Preview taskserv configuration generation
def preview-config-generation [
taskserv_name: string
taskserv_profile: string
settings: record
server: record
--verbose (-v)
] {
let taskservs_path = (get-taskservs-path)
let taskserv_dir = (find-taskserv-dir $taskservs_path $taskserv_name)
let profile_path = if ($taskserv_dir | is-not-empty) { $taskserv_dir | path join $taskserv_profile } else { "" }
if not ($profile_path | path exists) {
return {
valid: false
errors: [$"Profile path not found: ($profile_path)"]
warnings: []
files: []
}
}
# Find all template files
let template_files = (glob ($profile_path | path join "**/*.j2"))
# Find shell scripts
let script_files = (glob ($profile_path | path join "**/*.sh"))
# Find other config files
let config_files = (do -i {
ls $profile_path
| where type == "file"
| where name !~ ".j2$"
| where name !~ ".sh$"
| get name
} | default [])
mut preview_files = []
# Preview templates
for tpl in $template_files {
let dest_name = ($tpl | path basename | str replace ".j2" "")
$preview_files = ($preview_files | append {
type: "template"
source: ($tpl | path relative-to $profile_path)
destination: $dest_name
action: "render and upload"
})
}
# Preview scripts
for script in $script_files {
$preview_files = ($preview_files | append {
type: "script"
source: ($script | path basename)
destination: ($script | path basename)
action: "upload and execute"
})
}
# Preview config files
for cfg in $config_files {
$preview_files = ($preview_files | append {
type: "config"
source: ($cfg | path basename)
destination: ($cfg | path basename)
action: "upload"
})
}
return {
valid: true
errors: []
warnings: []
files: $preview_files
total_files: ($preview_files | length)
}
}
# Check prerequisites on target server (without actually connecting in check mode)
def check-prerequisites [
taskserv_name: string
server: record
settings: record
check_mode: bool
] {
mut checks = []
# Check if server is accessible (in check mode, just validate config)
if $check_mode {
$checks = ($checks | append {
check: "Server accessibility"
status: "skipped"
message: "Check mode - SSH not tested"
})
} else {
# In real mode, this would test SSH connection
$checks = ($checks | append {
check: "Server accessibility"
status: "pending"
message: "Would test SSH connection"
})
}
# Check if required directories exist (preview only in check mode)
let required_dirs = ["/tmp", "/etc", "/usr/local/bin"]
for dir in $required_dirs {
$checks = ($checks | append {
check: $"Directory ($dir)"
status: "info"
message: $"Would verify directory exists"
})
}
# Check if required commands are available
let required_commands = ["bash", "systemctl"]
for cmd in $required_commands {
$checks = ($checks | append {
check: $"Command ($cmd)"
status: "info"
message: $"Would verify command is available"
})
}
return {
checks: $checks
total_checks: ($checks | length)
}
}
# Enhanced check mode handler
export def run-check-mode [
taskserv_name: string
taskserv_profile: string
settings: record
server: record
--verbose (-v)
] {
_print $"\n(_ansi cyan_bold)Check Mode: ($taskserv_name)(_ansi reset) on (_ansi green_bold)($server.hostname)(_ansi reset)"
mut results = {
taskserv: $taskserv_name
profile: $taskserv_profile
server: $server.hostname
validations: []
overall_valid: true
}
# 1. Static validation
_print $"\n(_ansi yellow)→ Running static validation...(_ansi reset)"
let static_validation = (run-static-validation $taskserv_name --verbose=$verbose)
let static_valid = (
$static_validation.nickel.valid and
$static_validation.templates.valid and
$static_validation.scripts.valid
)
if $static_valid {
_print $" (_ansi green)✓ Static validation passed(_ansi reset)"
} else {
_print $" (_ansi red)✗ Static validation failed(_ansi reset)"
$results.overall_valid = false
}
$results.validations = ($results.validations | append {
level: "static"
valid: $static_valid
details: $static_validation
})
# 2. Dependency validation
_print $"\n(_ansi yellow)→ Checking dependencies...(_ansi reset)"
let deps_validation = (validate-dependencies $taskserv_name $settings --verbose=$verbose)
if $deps_validation.valid {
_print $" (_ansi green)✓ Dependencies OK(_ansi reset)"
if ($deps_validation.warnings | default [] | length) > 0 {
_print $" Warnings: (($deps_validation.warnings | str join ', '))"
}
} else {
_print $" (_ansi red)✗ Dependency issues found(_ansi reset)"
for err in ($deps_validation.errors | default []) {
_print $" (_ansi red)✗(_ansi reset) ($err)"
}
$results.overall_valid = false
}
$results.validations = ($results.validations | append {
level: "dependencies"
valid: $deps_validation.valid
details: $deps_validation
})
# 3. Preview configuration generation
_print $"\n(_ansi yellow)→ Previewing configuration generation...(_ansi reset)"
let config_preview = (preview-config-generation $taskserv_name $taskserv_profile $settings $server --verbose=$verbose)
if $config_preview.valid {
_print $" (_ansi green)✓ Configuration preview generated(_ansi reset)"
_print $" Files to process: ($config_preview.total_files)"
if $verbose and ($config_preview.files | length) > 0 {
_print $"\n Files to be deployed:"
for file in $config_preview.files {
_print $" ($file.type): ($file.source) → ($file.destination)"
}
}
} else {
_print $" (_ansi red)✗ Configuration preview failed(_ansi reset)"
$results.overall_valid = false
}
$results.validations = ($results.validations | append {
level: "configuration"
valid: $config_preview.valid
details: $config_preview
})
# 4. Prerequisites check
_print $"\n(_ansi yellow)→ Checking prerequisites...(_ansi reset)"
let prereq_check = (check-prerequisites $taskserv_name $server $settings true)
let mode_label = "(preview mode)"
_print $" (_ansi blue)(_ansi reset) Prerequisite checks ($mode_label):"
for check in $prereq_check.checks {
let icon = match $check.status {
"passed" => $"(_ansi green)✓(_ansi reset)"
"failed" => $"(_ansi red)✗(_ansi reset)"
"info" => $"(_ansi blue)(_ansi reset)"
"skipped" => $"(_ansi yellow)⊘(_ansi reset)"
_ => "•"
}
_print $" ($icon) ($check.check): ($check.message)"
}
$results.validations = ($results.validations | append {
level: "prerequisites"
valid: true
details: $prereq_check
})
# Summary
_print $"\n(_ansi cyan_bold)Check Mode Summary(_ansi reset)"
if $results.overall_valid {
_print $"(_ansi green_bold)✓ All validations passed(_ansi reset)"
_print $"\n💡 Taskserv can be deployed with: (_ansi cyan)provisioning taskserv create ($taskserv_name)(_ansi reset)"
} else {
_print $"(_ansi red_bold)✗ Validation failed(_ansi reset)"
_print $"\n🛑 Fix the errors above before deploying"
}
return $results
}
# Print detailed check mode report
export def print-check-report [
results: record
--format: string = "text"
] {
match $format {
"json" => {
$results | to json
}
"yaml" => {
$results | to yaml
}
_ => {
# Text format already printed by run-check-mode
null
}
}
}
# Upload taskserv scripts to server for inspection WITHOUT executing them.
# defs must include: settings, server, taskserv, ip (real), taskserv_dir, taskserv_profile
export def run-upload-inspection [
defs: record
--verbose (-v)
]: nothing -> record {
let name = $defs.taskserv.name
let check_dir = $"/tmp/prvng-check/($name)"
let ip = $defs.ip
let profile_path = ($defs.taskserv_dir | path join $defs.taskserv_profile)
_print $"\n(_ansi cyan_bold)Upload Inspection: ($name)(_ansi reset) → (_ansi green_bold)($defs.server.hostname)(_ansi reset) [($ip)]"
if not ($profile_path | path exists) {
_print $" (_ansi red)✗(_ansi reset) Profile path not found: ($profile_path)"
return {
valid: false
check_dir: $check_dir
uploaded_files: []
syntax_ok: false
errors: [$"Profile path not found: ($profile_path)"]
}
}
# Enumerate local files to report
let file_list = (do -i { ls $profile_path | where type == "file" | get name } | default [])
# Pack profile dir into local temp tar
let tar_path = $"/tmp/prvng-check-($name).tar.gz"
let pack_result = (do { ^tar -C $profile_path -czf $tar_path . } | complete)
if $pack_result.exit_code != 0 {
_print $" (_ansi red)✗(_ansi reset) Failed to pack: ($pack_result.stderr)"
return { valid: false, check_dir: $check_dir, uploaded_files: [], syntax_ok: false, errors: ["Pack failed"] }
}
# SSH: create inspection directory
if not (ssh_cmd $defs.settings $defs.server false $"mkdir -p ($check_dir)" $ip) {
rm -f $tar_path
_print $" (_ansi red)✗(_ansi reset) SSH connection failed — cannot create ($check_dir)"
return { valid: false, check_dir: $check_dir, uploaded_files: [], syntax_ok: false, errors: ["SSH mkdir failed"] }
}
# SCP: upload tar to /tmp on server
if not (scp_to $defs.settings $defs.server [$tar_path] "/tmp" $ip) {
rm -f $tar_path
_print $" (_ansi red)✗(_ansi reset) SCP upload failed"
return { valid: false, check_dir: $check_dir, uploaded_files: [], syntax_ok: false, errors: ["SCP failed"] }
}
rm -f $tar_path
# SSH: extract bundle into check_dir — no execute
let extract_cmd = $"cd ($check_dir) && tar -xzf /tmp/prvng-check-($name).tar.gz && rm -f /tmp/prvng-check-($name).tar.gz"
if not (ssh_cmd $defs.settings $defs.server false $extract_cmd $ip) {
_print $" (_ansi red)✗(_ansi reset) Extraction on server failed"
return { valid: false, check_dir: $check_dir, uploaded_files: ($file_list | each { |f| $f | path basename }), syntax_ok: false, errors: ["Extract failed"] }
}
# SSH: bash -n syntax check on all uploaded .sh files (no execution)
let syntax_cmd = $"find ($check_dir) -name '*.sh' -exec bash -n \\{\\} \\;"
let syntax_ok = (ssh_cmd $defs.settings $defs.server false $syntax_cmd $ip)
let basenames = ($file_list | each { |f| $f | path basename })
if $verbose {
_print $" Files uploaded from ($profile_path):"
for f in $basenames {
_print $" ($f)"
}
}
let syntax_label = if $syntax_ok {
$"(_ansi green)✓(_ansi reset) bash -n syntax OK"
} else {
$"(_ansi red)✗(_ansi reset) Syntax errors found — see SSH output above"
}
_print $" (_ansi green)✓(_ansi reset) Uploaded to (_ansi cyan)($check_dir)(_ansi reset) — not executed"
_print $" ($syntax_label)"
_print $" Inspect : (_ansi blue)ssh ($defs.server.installer_user)@($ip) ls -la ($check_dir)/(_ansi reset)"
_print $" Cleanup : (_ansi blue)ssh ($defs.server.installer_user)@($ip) rm -rf ($check_dir)(_ansi reset)"
{
valid: $syntax_ok
check_dir: $check_dir
server: $defs.server.hostname
ip: $ip
syntax_ok: $syntax_ok
uploaded_files: $basenames
errors: (if $syntax_ok { [] } else { ["Script syntax errors detected remotely"] })
}
}