provisioning-catalog/providers/prov_lib/provider_state.nu

102 lines
4.2 KiB
Text
Raw Permalink Normal View History

#!/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"]}
}