412 lines
15 KiB
Text
412 lines
15 KiB
Text
use std
|
|
# Selective imports replacing fat-path (ADR-025 Phase 4).
|
|
use lib_provisioning/platform/target.nu [detect-platform-mode]
|
|
use lib_provisioning/utils/clean.nu [cleanup]
|
|
use lib_provisioning/utils/interface.nu [_print]
|
|
use lib_provisioning/utils/service-check.nu [verify-service-or-fail]
|
|
use lib_provisioning/utils/simple_validation.nu [check-command]
|
|
|
|
# Comprehensive workflow management commands
|
|
|
|
# Get orchestrator endpoint from platform configuration or use provided default
|
|
def get-orchestrator-url [--orchestrator: string = ""] {
|
|
if ($orchestrator | is-not-empty) {
|
|
return $orchestrator
|
|
}
|
|
|
|
# Try to get from environment variable first
|
|
if ($env.PROVISIONING_ORCHESTRATOR_URL? | is-not-empty) {
|
|
return $env.PROVISIONING_ORCHESTRATOR_URL
|
|
}
|
|
|
|
# Skip slow platform discovery - just use localhost default
|
|
# (Platform discovery via nickel export is too slow for CLI responsiveness)
|
|
# Users can set PROVISIONING_ORCHESTRATOR_URL to override
|
|
"http://localhost:9011"
|
|
}
|
|
|
|
# Detect if orchestrator URL is local (for plugin usage)
|
|
def use-local-plugin [orchestrator_url: string] {
|
|
# Check if it's a local endpoint
|
|
(detect-platform-mode $orchestrator_url) == "local"
|
|
}
|
|
|
|
# List all active workflows - interactive loop
|
|
export def "workflow list" [
|
|
limit?: int # Number of recent tasks to show (default: 10)
|
|
--orchestrator: string = "" # Orchestrator URL (optional, uses platform config if not provided)
|
|
--status: string # Filter by status: Pending, Running, Completed, Failed, Cancelled
|
|
] {
|
|
let orch_url = (get-orchestrator-url --orchestrator=$orchestrator)
|
|
let task_limit = ($limit | default 10)
|
|
|
|
let avail = (verify-service-or-fail $orch_url "Orchestrator"
|
|
--check-command "provisioning platform status"
|
|
--check-alias "prvng plat st"
|
|
--start-command "provisioning platform start orchestrator"
|
|
--start-alias "prvng plat start orchestrator"
|
|
)
|
|
if $avail.status == "error" { return }
|
|
|
|
mut continue_browsing = true
|
|
|
|
while $continue_browsing {
|
|
# Always use HTTP API - plugin doesn't return tasks reliably
|
|
let response = (http get $"($orch_url)/tasks")
|
|
|
|
if not ($response | get success) {
|
|
_print $"Error: (($response | get error))"
|
|
break
|
|
}
|
|
|
|
let tasks = ($response | get data)
|
|
|
|
let filtered_tasks = if ($status | is-not-empty) {
|
|
$tasks | where status == $status
|
|
} else {
|
|
$tasks
|
|
}
|
|
|
|
# Limit to specified number of recent tasks
|
|
let limited_tasks = (
|
|
if ($filtered_tasks | length) > $task_limit {
|
|
$filtered_tasks | reverse | first $task_limit | reverse
|
|
} else {
|
|
$filtered_tasks
|
|
}
|
|
)
|
|
|
|
# Format tasks as numbered table for clean display
|
|
mut formatted = []
|
|
mut row_num = 1
|
|
for task in $limited_tasks {
|
|
let status_display = if $task.status == "Failed" {
|
|
((ansi red) + $task.status + (ansi reset))
|
|
} else {
|
|
$task.status
|
|
}
|
|
$formatted = ($formatted | append {
|
|
"#": $row_num,
|
|
"Task ID": $task.id,
|
|
"Status": $status_display,
|
|
"Completed At": ($task.completed_at | default "N/A")
|
|
})
|
|
$row_num = ($row_num + 1)
|
|
}
|
|
|
|
# Display as native Nushell table without index column
|
|
print ($formatted | table --index false)
|
|
|
|
_print ""
|
|
_print "0 = Exit, or enter task number:"
|
|
_print ""
|
|
|
|
# Get task number from user
|
|
let task_num_str = (typedialog text "Task number:" --default "0")
|
|
|
|
# Simple validation - just try to convert
|
|
let task_num = ($task_num_str | str trim | into int)
|
|
|
|
if $task_num == 0 {
|
|
$continue_browsing = false
|
|
} else if $task_num < 1 or $task_num > ($limited_tasks | length) {
|
|
_print $"❌ Invalid task number. Choose 1-($limited_tasks | length), or 0 to exit"
|
|
_print ""
|
|
} else {
|
|
let task_index = ($task_num - 1)
|
|
let selected_task = ($limited_tasks | get $task_index)
|
|
let task_id = $selected_task.id
|
|
|
|
_print ""
|
|
_print $"📊 Status for: ($task_id)"
|
|
_print "════════════════════════════════════════════════"
|
|
workflow status $task_id --orchestrator $orch_url
|
|
_print ""
|
|
_print ""
|
|
_print "─────────────────────────────────────────────────"
|
|
let continue_choice = (typedialog select "¿Qué deseas hacer?" ["Continuar" "Salir"])
|
|
|
|
if $continue_choice == "Salir" {
|
|
$continue_browsing = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Get detailed workflow status
|
|
export def "workflow status" [
|
|
task_id: string # Task ID to check
|
|
--orchestrator: string = "" # Orchestrator URL (optional, uses platform config if not provided)
|
|
] {
|
|
let orch_url = (get-orchestrator-url --orchestrator=$orchestrator)
|
|
|
|
let avail = (verify-service-or-fail $orch_url "Orchestrator"
|
|
--check-command "provisioning platform status"
|
|
--check-alias "prvng plat st"
|
|
--start-command "provisioning platform start orchestrator"
|
|
--start-alias "prvng plat start orchestrator"
|
|
)
|
|
if $avail.status == "error" { return { error: "Orchestrator not available" } }
|
|
|
|
# Always use HTTP API - plugin doesn't return tasks reliably
|
|
let response = (http get $"($orch_url)/tasks/($task_id)")
|
|
|
|
if not ($response | get success) {
|
|
return { error: ($response | get error) }
|
|
}
|
|
|
|
let task = ($response | get data)
|
|
|
|
# Convert arrays to strings for display, then transpose to vertical format
|
|
let displayable = {
|
|
id: $task.id,
|
|
name: $task.name,
|
|
command: $task.command,
|
|
args: ($task.args | str join "\n "),
|
|
dependencies: ($task.dependencies | str join "\n "),
|
|
status: $task.status,
|
|
created_at: $task.created_at,
|
|
started_at: ($task.started_at | default "N/A"),
|
|
completed_at: ($task.completed_at | default "N/A"),
|
|
output: ($task.output | default ""),
|
|
error: ($task.error | default "")
|
|
}
|
|
|
|
# Convert to vertical key-value table with proper headers
|
|
print ($displayable | transpose | each {|row| {Field: $row.column0, Value: $row.column1}} | table -i false)
|
|
}
|
|
|
|
# Monitor workflow progress in real-time
|
|
export def "workflow monitor" [
|
|
task_id: string # Task ID to monitor
|
|
--orchestrator: string = "" # Orchestrator URL (optional, uses platform config if not provided)
|
|
] {
|
|
let orch_url = (get-orchestrator-url --orchestrator=$orchestrator)
|
|
|
|
_print $"Monitoring workflow: ($task_id)"
|
|
_print "Press Ctrl+C to stop monitoring"
|
|
_print ""
|
|
|
|
while true {
|
|
let task = (workflow status $task_id --orchestrator $orch_url)
|
|
|
|
let err_result = (do { $task | get error } | complete)
|
|
let task_error = if $err_result.exit_code == 0 { $err_result.stdout } else { null }
|
|
if ($task_error | is-not-empty) {
|
|
_print $"❌ Error getting task status: ($task_error)"
|
|
break
|
|
}
|
|
|
|
let status = ($task | get status)
|
|
let created = ($task | get created_at)
|
|
let start_result = (do { $task | get started_at } | complete)
|
|
let started = if $start_result.exit_code == 0 { $start_result.stdout } else { "Not started" }
|
|
let comp_result = (do { $task | get completed_at } | complete)
|
|
let completed = if $comp_result.exit_code == 0 { $comp_result.stdout } else { "Not completed" }
|
|
|
|
clear
|
|
_print $"📊 Workflow Status: ($task_id)"
|
|
_print $"═══════════════════════════════════════════════════════════════"
|
|
_print $"Name: (($task | get name))"
|
|
_print $"Status: ($status)"
|
|
_print $"Created: ($created)"
|
|
_print $"Started: ($started)"
|
|
_print $"Completed: ($completed)"
|
|
_print ""
|
|
|
|
match $status {
|
|
"Completed" => {
|
|
_print "✅ Workflow completed successfully!"
|
|
let out_result = (do { $task | get output } | complete)
|
|
let task_output = if $out_result.exit_code == 0 { $out_result.stdout } else { null }
|
|
if ($task_output | is-not-empty) {
|
|
_print ""
|
|
_print "Output:"
|
|
_print "───────"
|
|
_print $task_output
|
|
}
|
|
break
|
|
},
|
|
"Failed" => {
|
|
_print "❌ Workflow failed!"
|
|
let err_result = (do { $task | get error } | complete)
|
|
let task_error = if $err_result.exit_code == 0 { $err_result.stdout } else { null }
|
|
if ($task_error | is-not-empty) {
|
|
_print ""
|
|
_print "Error:"
|
|
_print "──────"
|
|
_print $task_error
|
|
}
|
|
break
|
|
},
|
|
"Running" => {
|
|
_print "🔄 Workflow is running..."
|
|
},
|
|
"Cancelled" => {
|
|
_print "🚫 Workflow was cancelled"
|
|
break
|
|
},
|
|
_ => {
|
|
_print $"⏳ Status: ($status)"
|
|
}
|
|
}
|
|
|
|
_print ""
|
|
_print "Refreshing in 3 seconds... (Ctrl+C to stop)"
|
|
sleep 3sec
|
|
}
|
|
}
|
|
|
|
# Show workflow statistics
|
|
export def "workflow stats" [
|
|
--orchestrator: string = "" # Orchestrator URL (optional, uses platform config if not provided)
|
|
] {
|
|
let orch_url = (get-orchestrator-url --orchestrator=$orchestrator)
|
|
|
|
let avail = (verify-service-or-fail $orch_url "Orchestrator"
|
|
--check-command "provisioning platform status"
|
|
--check-alias "prvng plat st"
|
|
--start-command "provisioning platform start orchestrator"
|
|
--start-alias "prvng plat start orchestrator"
|
|
)
|
|
if $avail.status == "error" { return }
|
|
|
|
let response = (http get $"($orch_url)/tasks")
|
|
if not ($response | get success) {
|
|
_print $"Error: (($response | get error))"
|
|
return
|
|
}
|
|
let tasks = ($response | get data)
|
|
|
|
let total = ($tasks | length)
|
|
let completed = ($tasks | where status == "Completed" | length)
|
|
let failed = ($tasks | where status == "Failed" | length)
|
|
let running = ($tasks | where status == "Running" | length)
|
|
let pending = ($tasks | where status == "Pending" | length)
|
|
let cancelled = ($tasks | where status == "Cancelled" | length)
|
|
|
|
{
|
|
total: $total,
|
|
completed: $completed,
|
|
failed: $failed,
|
|
running: $running,
|
|
pending: $pending,
|
|
cancelled: $cancelled,
|
|
success_rate: (if $total > 0 { ($completed / $total * 100) | math round | into int } else { 0 })
|
|
}
|
|
}
|
|
|
|
# Clean up old completed workflows
|
|
export def "workflow cleanup" [
|
|
--orchestrator: string = "" # Orchestrator URL (optional, uses platform config if not provided)
|
|
--days: int = 7 # Remove workflows older than this many days
|
|
--dry-run # Show what would be removed without actually removing
|
|
] {
|
|
let orch_url = (get-orchestrator-url --orchestrator=$orchestrator)
|
|
|
|
_print $"Cleaning up workflows older than ($days) days..."
|
|
|
|
let cutoff_date = ((date now) - ($days * 86400 | into duration --unit sec))
|
|
let tasks = (workflow list --orchestrator $orch_url)
|
|
|
|
let old_tasks = ($tasks | where {|task|
|
|
let task_date = ($task.completed_at | into datetime)
|
|
$task_date < $cutoff_date and ($task.status in ["Completed", "Failed", "Cancelled"])
|
|
})
|
|
|
|
if ($old_tasks | length) == 0 {
|
|
_print "No old workflows found for cleanup."
|
|
return
|
|
}
|
|
|
|
_print $"Found ($old_tasks | length) workflows for cleanup:"
|
|
$old_tasks | select id name status completed_at | table
|
|
|
|
if $dry_run {
|
|
_print ""
|
|
_print "Dry run mode - no workflows were actually removed."
|
|
_print "Run without --dry-run to perform the cleanup."
|
|
} else {
|
|
_print ""
|
|
_print "Note: Actual cleanup would require orchestrator API support for deletion."
|
|
_print "This is a placeholder for future implementation."
|
|
}
|
|
}
|
|
|
|
# Orchestrator health and info
|
|
export def "workflow orchestrator" [
|
|
--orchestrator: string = "http://localhost:9011" # Orchestrator URL
|
|
] {
|
|
let avail = (verify-service-or-fail $orchestrator "Orchestrator"
|
|
--check-command "provisioning platform status"
|
|
--check-alias "prvng plat st"
|
|
--start-command "provisioning platform start orchestrator"
|
|
--start-alias "prvng plat start orchestrator"
|
|
)
|
|
if $avail.status == "error" {
|
|
return {
|
|
status: "unreachable",
|
|
message: "Cannot connect to orchestrator",
|
|
orchestrator_url: $orchestrator
|
|
}
|
|
}
|
|
|
|
# Always use HTTP API for consistency
|
|
let health_response = (http get $"($orchestrator)/health")
|
|
let stats = (workflow stats --orchestrator $orchestrator)
|
|
|
|
if not ($health_response | get success) {
|
|
return {
|
|
status: "unreachable",
|
|
message: "Cannot connect to orchestrator",
|
|
orchestrator_url: $orchestrator
|
|
}
|
|
}
|
|
|
|
{
|
|
status: "healthy",
|
|
message: ($health_response | get data),
|
|
orchestrator_url: $orchestrator,
|
|
workflow_stats: $stats
|
|
}
|
|
}
|
|
|
|
# Interactive workflow browser - alias to list
|
|
export def "workflow browse" [
|
|
limit?: int # Number of recent tasks to show (default: 10)
|
|
--orchestrator: string = "" # Orchestrator URL (optional)
|
|
] {
|
|
if ($limit | is-not-empty) {
|
|
workflow list $limit --orchestrator $orchestrator
|
|
} else {
|
|
workflow list --orchestrator $orchestrator
|
|
}
|
|
}
|
|
|
|
# Submit workflows with dependency management
|
|
export def "workflow submit" [
|
|
workflow_type: string # server, taskserv, cluster
|
|
operation: string # create, delete, etc.
|
|
target: string # specific target (server name, taskserv name, etc.)
|
|
infra?: string # Infrastructure target
|
|
settings?: string # Settings file path
|
|
--depends-on: list<string> = [] # Task IDs this workflow depends on
|
|
--check (-c) # Check mode only
|
|
--wait (-w) # Wait for completion
|
|
--orchestrator: string = "http://localhost:8080" # Orchestrator URL
|
|
] {
|
|
match $workflow_type {
|
|
"server" => {
|
|
_print "Server workflow creation not yet implemented"
|
|
},
|
|
"taskserv" => {
|
|
_print "Taskserv workflow not yet implemented"
|
|
},
|
|
"cluster" => {
|
|
_print "Cluster workflow not yet implemented"
|
|
},
|
|
_ => {
|
|
_print $"Unknown workflow type: ($workflow_type)"
|
|
}
|
|
}
|
|
}
|