545 lines
17 KiB
Plaintext
Raw Normal View History

2025-10-07 11:05:08 +01:00
# Provider-Agnostic Middleware
# Uses dynamic provider loading and interface-based dispatch
# Supports multi-provider infrastructure deployments
use ../../../core/nulib/lib_provisioning/config/accessor.nu *
use ../../../core/nulib/lib_provisioning/extensions/profiles.nu *
use ../../../core/nulib/lib_provisioning/providers/registry.nu *
use ../../../core/nulib/lib_provisioning/providers/loader.nu *
use ../../../core/nulib/lib_provisioning/providers/interface.nu *
use ../../../core/nulib/lib_provisioning/utils/logging.nu *
# Initialize middleware
export def init-middleware []: nothing -> nothing {
init-provider-registry
log-info "Provider-agnostic middleware initialized" "middleware"
}
# Check if provider is allowed by profile (safer version)
def is_provider_allowed [provider_name: string]: nothing -> bool {
# Disabled - causes | complete issues
true
}
def provider_undefined [
server: record
] {
let server_prov = ($server | get -o provider | default "")
let available_providers = (list-providers --available-only | get name | str join ", ")
# This error is misleading - it's called when function dispatch fails, not when provider isn't found
# The actual issue is that the provider function call failed
log-error $"Provider function returned null for provider ($server_prov)" "middleware"
print $"(_ansi blue_bold)($server.hostname)(_ansi reset) with provider " +
$"(_ansi green_bold)($server_prov)(_ansi reset) ($server.zone?) "
print $"Note: Provider was loaded but function call failed or returned empty result"
}
# Dynamic provider dispatch helper
def dispatch_provider_function [
provider_name: string
function_name: string
...args
]: nothing -> any {
# Check if provider is allowed
if not (is_provider_allowed $provider_name) {
log-error $"Provider ($provider_name) blocked by profile" "middleware"
return null
}
# Load provider if not already loaded
let provider = (get-provider $provider_name)
if ($provider | is-empty) {
log-error $"Failed to load provider ($provider_name)" "middleware"
return null
}
# Call provider function
call-provider-function $provider_name $function_name ...$args
}
# === CORE MIDDLEWARE FUNCTIONS ===
# Query servers (supports multi-provider)
export def mw_query_servers [
settings: record
find?: string
cols?: string
--prov: string
--serverpos: int
]: nothing -> list {
if not ($env.PROVIDER_REGISTRY_INITIALIZED? | default false) {
init-middleware | ignore
}
let str_find = if $find != null { $find } else { "" }
let str_cols = if $cols != null { $cols } else { "" }
$settings.data.servers | enumerate | each { |it|
if $prov == null or $it.item.provider == $prov {
if $serverpos == null or $serverpos == $it.index {
let provider_name = $it.item.provider
# Dynamic provider dispatch
let res = (dispatch_provider_function $provider_name "query_servers" $str_find $str_cols )
if $res == null {
provider_undefined $it.item
[]
} else {
$res
}
if ($res != null) and ($res | length) > 0 {
let result = if $str_find != "" {
$res | find $str_find
} else {
$res
}
if $str_cols != "" {
let field_list = ($str_cols | split row ",")
($result | select -o $field_list)
} else {
$result
}
} else {
[]
}
}
}
} | flatten
}
# Server information (provider-agnostic)
export def mw_server_info [
server: record
check: bool
find?: string
cols?: string
]: nothing -> record {
let str_find = if $find != null { $find } else { "" }
let str_cols = if $cols != null { $cols } else { "" }
let res = (dispatch_provider_function $server.provider "server_info" $server $check $str_find $str_cols )
if $res == null {
provider_undefined $server
{}
} else {
$res
}
if ($res | describe | str starts-with "record") and $res.hostname? != null {
let result = if $str_find != "" {
$res | find $str_find
} else {
$res
}
let info = if $str_cols != "" {
let field_list = ($str_cols | split row ",")
($result | select -o $field_list)
} else {
$result
}
# Handle provider-specific private IP formats
let priv = if $server.provider == "aws" {
($info | get -o private_ips | default [] | each {|it| ($it | select Description PrivateIpAddress VpcId SubnetId Groups) })
} else {
($info | get -o priv | default [])
}
let full_info = if ($priv | length) > 0 {
($info | merge { private_ips: $priv })
} else {
$info
}
let out = (get-provisioning-out)
if ($out | is-empty) {
print ($full_info | table -e)
}
if (not $check) {
($full_info | table -e)
}
$full_info
} else {
$res
}
}
# Server creation (provider-agnostic with profile checking)
export def mw_create_server [
settings: record
server: record
check: bool
error_exit: bool
]: nothing -> bool {
# Check if provider is allowed by profile
if not (is_provider_allowed $server.provider) {
log-error $"Provider ($server.provider) blocked by current profile" "middleware"
if $error_exit { exit } else { return false }
}
let zone = $server.zone? | default ""
# Dynamic provider dispatch for requirements check
let res = (dispatch_provider_function $server.provider "check_server_requirements" $settings $server $check)
if $res == null {
provider_undefined $server
if $error_exit { exit } else { return false }
}
if not $res {
log-error $"($server.provider) check requirements error for server ($server.hostname)" "middleware"
return false
}
# Provider-specific message (most providers don't implement this, so we skip it)
# If needed in future, provider can override the default "Create..." message below
_print ($"Create (_ansi blue_bold)($server.hostname)(_ansi reset) with provider " +
$"(_ansi green_bold)($server.provider)(_ansi reset) ($zone) ")
return true
}
# Server state management (provider-agnostic)
export def mw_server_state [
server: record
new_state: string
error_exit: bool
wait: bool
settings: record
]: nothing -> bool {
# Check if provider is allowed by profile
if not (is_provider_allowed $server.provider) {
log-error $"Provider ($server.provider) blocked by current profile" "middleware"
if $error_exit { exit } else { return false }
}
let res = (dispatch_provider_function $server.provider "server_state" $server $new_state $error_exit $wait $settings )
if $res == null {
provider_undefined $server
if $error_exit { exit } else { return false }
} else {
let result = $res
$result != null
}
}
# Server existence check (provider-agnostic)
export def mw_server_exists [
server: record
error_exit: bool
]: nothing -> bool {
let res = (dispatch_provider_function $server.provider "server_exists" $server $error_exit )
if $res == null {
provider_undefined $server
if $error_exit { exit } else { false }
} else {
let result = $res
$result != null
}
}
# Server running status (provider-agnostic)
export def mw_server_is_running [
server: record
error_exit: bool
]: nothing -> bool {
let res = (dispatch_provider_function $server.provider "server_is_running" $server $error_exit )
if $res == null {
provider_undefined $server
if $error_exit { exit } else { false }
} else {
let result = $res
$result != null
}
}
# Get IP (provider-agnostic)
export def mw_get_ip [
settings: record
server: record
ip_type: string
error_exit: bool
]: nothing -> string {
let res = (dispatch_provider_function $server.provider "get_ip" $settings $server $ip_type $error_exit )
if $res == null {
provider_undefined $server
if $error_exit { exit } else { "" }
} else {
let result = $res
if $result != null {
$result | str trim
} else {
""
}
}
}
# Multi-provider infrastructure operations
export def mw_servers_ips [
settings: record
data: list
prov?: string
serverpos?: int
]: nothing -> list {
mut result = []
for srv in ($data | enumerate) {
let settings_server = ($settings.data.servers | where {|it| $it.hostname == $srv.item.hostname})
if ($settings_server | length) == 0 { continue }
let provider = ($settings_server | get -o 0 | get -o provider | default "")
if $prov != null and $provider != $prov { continue }
if $serverpos != null and $serverpos != $srv.index { continue }
let res = (dispatch_provider_function $provider "servers_ips" $settings [$srv.item] $prov $serverpos )
if $res == null {
provider_undefined { provider: $provider, hostname: $srv.item.hostname }
} else {
let provider_result = $res
if $provider_result != null {
$result = ($result | append $provider_result)
}
}
}
$result
}
# Multi-provider server deletion
export def mw_delete_server [
settings: record
server: record
keep_storage: bool
error_exit: bool
]: nothing -> bool {
let zone = $server.zone? | default ""
# Show provider-specific server message
let msg_result = (dispatch_provider_function $server.provider "on_prov_server" $server )
if ($msg_result != null) {
print $msg_result
}
# Delete server
let res = (dispatch_provider_function $server.provider "delete_server" $settings $server $keep_storage $error_exit )
if $res == null {
provider_undefined $server
if $error_exit { exit } else { false }
} else {
let result = $res
$result != null
}
}
# Load infrastructure servers info (provider-agnostic)
export def mw_load_infra_servers_info [
settings: record
server: record
error_exit: bool
]: nothing -> record {
let res = (dispatch_provider_function $server.provider "load_infra_servers_info" $settings $server $error_exit)
if $res == null {
provider_undefined $server
if $error_exit { exit } else { {} }
} else {
$res
}
}
# Load infrastructure storages info (provider-agnostic)
export def mw_load_infra_storages_info [
settings: record
server: record
error_exit: bool
]: nothing -> record {
let res = (dispatch_provider_function $server.provider "load_infra_storages_info" $settings $server $error_exit)
if $res == null {
provider_undefined $server
if $error_exit { exit } else { {} }
} else {
$res
}
}
# Get infrastructure item for server (provider-agnostic)
export def mw_get_infra_item [
server: record
settings: record
cloud_data: record
error_exit: bool
]: nothing -> record {
let res = (dispatch_provider_function $server.provider "get_infra_item" $server $settings $cloud_data $error_exit)
if $res == null {
provider_undefined $server
if $error_exit { exit } else { {} }
} else {
$res
}
}
# Get infrastructure price (provider-agnostic)
export def mw_get_infra_price [
server: record
data: record
key: string
error_exit: bool
price_col?: string
]: nothing -> any {
let res = (dispatch_provider_function $server.provider "get_infra_price" $server $data $key $error_exit $price_col)
if $res == null {
return (if $error_exit { (exit) } else { 0.0 })
}
# Convert result to float (wrapper scripts return strings)
let res_type = ($res | describe)
if ($res_type | str starts-with "float") or ($res_type | str starts-with "int") {
$res
} else if ($res_type | str starts-with "string") {
# Special handling for "unit" key which returns string like "0.0104 Hrs"
if $key == "unit" {
$res
} else {
# Parse string as float (wrapper scripts serialize numbers as strings)
# Check if string can be converted to float
let float_result = (do --ignore-errors { $res | into float })
if ($float_result | is-empty) {
0.0
} else {
$float_result
}
}
} else {
# Return 0.0 for any other non-numeric type
0.0
}
}
# Get infrastructure storage (provider-agnostic)
export def mw_get_infra_storage [
server: record
settings: record
cloud_data: record
error_exit: bool
]: nothing -> record {
let res = (dispatch_provider_function $server.provider "get_infra_storage" $server $settings $cloud_data $error_exit)
if $res == null {
if $error_exit { exit } else { {} }
} else {
$res
}
}
# === ENHANCED MULTI-PROVIDER OPERATIONS ===
# Deploy multi-provider infrastructure (simplified)
export def mw_deploy_multi_provider_infra [
settings: record
deployment_plan: record
]: nothing -> record {
log-section "Starting multi-provider deployment" "middleware"
# Group servers by provider
let provider_groups = ($settings.data.servers | group-by provider)
let providers_used = ($provider_groups | columns)
log-info $"Will deploy to providers: ($providers_used | str join ', ')" "middleware"
# Return basic deployment info
{
providers_used: $providers_used
total_servers: ($settings.data.servers | length)
deployment_plan: $deployment_plan
status: "planned"
timestamp: (date now)
}
}
# Get provider status with capabilities
export def mw_provider_status [
--verbose
]: nothing -> table {
if not ($env.PROVIDER_REGISTRY_INITIALIZED? | default false) {
init-middleware | ignore
}
let providers = (list-providers --available-only)
$providers | each {|provider|
let allowed = (is_provider_allowed $provider.name)
let capabilities = (get-provider-capabilities-for $provider.name)
let basic_info = {
name: $provider.name
type: ($provider.details? | get -o type | default "extension")
available: ($provider.details? | get -o available | default true)
loaded: ($provider.details? | get -o loaded | default false)
profile_allowed: $allowed
status: (if $allowed { "✅ Available" } else { "🛑 Blocked by profile" })
}
if $verbose {
$basic_info | merge {
capabilities: $provider.capabilities
server_management: ($capabilities.server_management? | default false)
multi_region: ($capabilities.multi_region? | default false)
auto_scaling: ($capabilities.auto_scaling? | default false)
}
} else {
$basic_info
}
}
}
# Get deployment recommendations for multi-provider setup
export def mw_suggest_deployment_strategy [
requirements: record
]: nothing -> record {
log-info "Analyzing requirements for deployment strategy" "middleware"
let available_providers = (list-providers --available-only)
mut recommendations = {
strategy: "single-provider" # default
primary_provider: ""
secondary_providers: []
rationale: []
}
# Analyze requirements and suggest strategy
if ($requirements.regions? | default [] | length) > 1 {
$recommendations.strategy = "multi-provider"
$recommendations.rationale = ($recommendations.rationale | append "Multi-region requirement detected")
}
if ($requirements.high_availability? | default false) {
$recommendations.strategy = "multi-provider"
$recommendations.rationale = ($recommendations.rationale | append "High availability requirement")
}
if ($requirements.cost_optimization? | default false) {
$recommendations.rationale = ($recommendations.rationale | append "Cost optimization: consider mixing providers")
}
# Select primary provider based on capabilities
let suitable_providers = ($available_providers | where {|p|
let caps = (get-provider-capabilities-for $p.name)
($caps.server_management? | default false) == true
})
if ($suitable_providers | length) > 0 {
$recommendations.primary_provider = ($suitable_providers | get 0 | get name)
if $recommendations.strategy == "multi-provider" {
$recommendations.secondary_providers = ($suitable_providers | skip 1 | get name)
}
}
log-info $"Recommended strategy: ($recommendations.strategy)" "middleware"
$recommendations
}
# Initialize middleware when loaded
export-env {
# This will be set when middleware functions are first called
}