use std use ../lib_provisioning * use ../lib_provisioning/platform * use ../lib_provisioning/utils/service-check.nu * # 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 = [] # 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)" } } }