#!/usr/bin/env nu # Provider State Management - Shared state change logic across providers # # This module consolidates all state transition logic: # - Server state changes (start/stop/restart) # - Storage state changes (online/available) # - Generic resource state polling # Generic state change polling loop def provider_state_loop [resource_name: string, get_status_fn: closure, target_state: string, timeout_secs: int, poll_interval_secs: int, elapsed_secs: int = 0] { let current_status = (do { $get_status_fn } | complete) if $current_status.exit_code != 0 { print $"Error getting status for ($resource_name)" return false } let status = $current_status.stdout if ($status | str contains $target_state) { return true } else if $timeout_secs > 0 and $elapsed_secs > $timeout_secs { print $"\nšŸ›‘ Timeout waiting for ($resource_name) to reach ($target_state)" return false } else { if (is-debug-enabled) { print $" Waiting for ($resource_name) -> ($status)" } else { print -n "." } sleep ($"($poll_interval_secs)sec" | into duration) provider_state_loop $resource_name $get_status_fn $target_state $timeout_secs $poll_interval_secs ($elapsed_secs + $poll_interval_secs) } } # Wait for server state change with provider-specific status function export def provider_wait_server_state [server: record, target_state: string, get_status_fn: closure, context: string = "server"] { let timeout = if $server.running_timeout? != null { $server.running_timeout } else { 60 } let wait_interval = if $server.running_wait? != null { $server.running_wait } else { 10 } print $"Waiting for ($context) to reach ($target_state)..." provider_state_loop $context $get_status_fn $target_state $timeout $wait_interval 0 } # Wait for storage/volume state change export def provider_wait_storage_state [server: record, storage_id: string, target_state: string, get_status_fn: closure, context: string = "storage"] { let timeout = if $server.storage_timeout? != null { $server.storage_timeout } else { 300 } let wait_interval = if $server.storage_wait? != null { $server.storage_wait } else { 5 } print $"Waiting for storage ($storage_id) to reach ($target_state)..." provider_state_loop $context $get_status_fn $target_state $timeout $wait_interval 0 } # Check if state is already in target state export def provider_state_is_current [current_state: string, target_state: string] { ($current_state | str contains $target_state) } # Validate state transition is allowed export def provider_state_transition_valid [current_state: string, target_state: string, allowed_transitions: record = {}] { if ($allowed_transitions | is-empty) { return true } if not ($allowed_transitions | has $current_state) { return true } let valid_targets = ($allowed_transitions | get $current_state) $target_state in $valid_targets } # Map provider-specific state names to standard names export def provider_normalize_state [state: string, state_mapping: record = {}] { if ($state_mapping | is-empty) { return $state } if ($state_mapping | has $state) { $state_mapping | get $state } else { $state } } # Extract numeric progress from state polling export def provider_extract_progress [state_response: record, progress_path: string = ""] { if ($progress_path | is-empty) { -1 } else { try { let parts = ($progress_path | split row ".") let result = ($parts | reduce -f $state_response {|part, obj| $obj | get $part }) if ($result | describe) == "int" { $result } else { -1 } } catch { -1 } } } # Format elapsed time for display export def provider_format_elapsed [seconds: int] { let minutes = ($seconds / 60) let secs = ($seconds mod 60) if $minutes > 0 { $"($minutes)m $($secs)s" } else { $"($secs)s" } } # Server state transition rules export def provider_server_transitions [] { {running: ["stopped"], stopped: ["running"], terminated: []} } # Storage state transition rules export def provider_storage_transitions [] { {available: ["in-use"], "in-use": ["available"], creating: ["available"], deleting: ["deleted"]} }