864 lines
27 KiB
Text
864 lines
27 KiB
Text
|
|
# Provider-Agnostic Middleware
|
||
|
|
# Uses dynamic provider loading and interface-based dispatch
|
||
|
|
# Supports multi-provider infrastructure deployments
|
||
|
|
|
||
|
|
use ../../../core/nulib/domain/config/accessor.nu *
|
||
|
|
use ../../../core/nulib/domain/providers/registry.nu *
|
||
|
|
use ../../../core/nulib/domain/providers/loader.nu *
|
||
|
|
use ../../../core/nulib/domain/providers/interface.nu *
|
||
|
|
use ../../../core/nulib/primitives/io/logging.nu *
|
||
|
|
use ../../../core/nulib/primitives/io/interface.nu [_ansi _print]
|
||
|
|
|
||
|
|
# Initialize middleware
|
||
|
|
export def init-middleware []: nothing -> nothing {
|
||
|
|
init-provider-registry
|
||
|
|
let daemon_mode = (($env.PROVISIONING_DAEMON_MODE? | default "false" | into string) == "true")
|
||
|
|
let quiet_middleware = (($env.PROVISIONING_QUIET_MIDDLEWARE? | default "false" | into string) == "true")
|
||
|
|
log-debug "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 { "" }
|
||
|
|
|
||
|
|
# Filter infrastructure servers: only enabled ones (not_use = false)
|
||
|
|
let enabled_servers = $settings.data.servers
|
||
|
|
| where {|s| ($s.enabled? | default true) and not ($s.not_use? | default false)}
|
||
|
|
| where {|s| $prov == null or $s.provider == $prov}
|
||
|
|
| where {|s| $serverpos == null or ($settings.data.servers | enumerate | where {|it| $it.item == $s} | first.index) == $serverpos}
|
||
|
|
|
||
|
|
# If no enabled servers match, return empty
|
||
|
|
if ($enabled_servers | length) == 0 {
|
||
|
|
return []
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get unique providers from enabled servers
|
||
|
|
let unique_providers = ($enabled_servers
|
||
|
|
| each {|s| $s.provider}
|
||
|
|
| uniq
|
||
|
|
)
|
||
|
|
|
||
|
|
# Get hostnames of enabled servers for filtering results
|
||
|
|
let enabled_hostnames = ($enabled_servers | each {|s| $s.hostname})
|
||
|
|
|
||
|
|
# Query each provider ONCE and filter by enabled infrastructure hostnames
|
||
|
|
$unique_providers | each { |provider_name|
|
||
|
|
let res = (dispatch_provider_function $provider_name "query_servers" $str_find $str_cols)
|
||
|
|
if $res == null {
|
||
|
|
provider_undefined {provider: $provider_name, hostname: "unknown", zone: ""}
|
||
|
|
[]
|
||
|
|
} else if ($res | length) > 0 {
|
||
|
|
# Filter results to only servers defined in enabled infrastructure
|
||
|
|
let filtered = $res | where {|server|
|
||
|
|
$server.name in $enabled_hostnames
|
||
|
|
}
|
||
|
|
|
||
|
|
let result = if $str_find != "" {
|
||
|
|
$filtered | find $str_find
|
||
|
|
} else {
|
||
|
|
$filtered
|
||
|
|
}
|
||
|
|
if $str_cols != "" {
|
||
|
|
let field_list = ($str_cols | split row ",")
|
||
|
|
($result | select ...$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 { "" }
|
||
|
|
|
||
|
|
# CRITICAL FIX: Pass $server.hostname (string), not $server (record)
|
||
|
|
# The provider's server_info function expects: server_info [server: string, check: bool, ...]
|
||
|
|
let res = (dispatch_provider_function $server.provider "server_info" $server.hostname $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 ...$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) and (is-debug-enabled) {
|
||
|
|
print ($full_info | table -e)
|
||
|
|
}
|
||
|
|
if (not $check) and (is-debug-enabled) {
|
||
|
|
($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 ""
|
||
|
|
|
||
|
|
# In check mode, skip provider requirements check (it may be slow or unavailable)
|
||
|
|
# Just report what would be created
|
||
|
|
if $check {
|
||
|
|
_print ($"Check (_ansi cyan_bold)($server.hostname)(_ansi reset) with provider " +
|
||
|
|
$"(_ansi green_bold)($server.provider)(_ansi reset) ($zone) ")
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
|
||
|
|
# Dynamic provider dispatch for requirements check (only for actual creation)
|
||
|
|
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 hostname = $server.hostname
|
||
|
|
let res = (dispatch_provider_function $server.provider "server_exists" $hostname $error_exit)
|
||
|
|
if $res == null {
|
||
|
|
provider_undefined $server
|
||
|
|
if $error_exit { exit } else { false }
|
||
|
|
} else {
|
||
|
|
$res | default false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Server running status (provider-agnostic)
|
||
|
|
export def mw_server_is_running [
|
||
|
|
server: record
|
||
|
|
error_exit: bool
|
||
|
|
]: nothing -> bool {
|
||
|
|
let hostname = $server.hostname
|
||
|
|
let res = (dispatch_provider_function $server.provider "server_is_running" $hostname $error_exit)
|
||
|
|
if $res == null {
|
||
|
|
provider_undefined $server
|
||
|
|
if $error_exit { exit } else { false }
|
||
|
|
} else {
|
||
|
|
$res | default false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Resolve template variables in ip_type (e.g. "{{network_public_ip}}" -> public)
|
||
|
|
def resolve_ip_template [
|
||
|
|
server: record
|
||
|
|
ip_type: string
|
||
|
|
]: nothing -> string {
|
||
|
|
# Remove surrounding quotes if present
|
||
|
|
let cleaned = ($ip_type | str trim --char "\"")
|
||
|
|
|
||
|
|
# Check if ip_type contains template variable {{...}}
|
||
|
|
if ($cleaned | str contains "{{") and ($cleaned | str contains "}}") {
|
||
|
|
# Extract field name between {{ and }}
|
||
|
|
let without_prefix = ($cleaned | str replace "{{" "")
|
||
|
|
let field_name = ($without_prefix | str replace "}}" "")
|
||
|
|
# Return the value of that field from server, or "public" as fallback
|
||
|
|
($server | get -o $field_name | default "public")
|
||
|
|
} else {
|
||
|
|
# Not a template variable, return as-is
|
||
|
|
$cleaned
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get IP (provider-agnostic)
|
||
|
|
export def mw_get_ip [
|
||
|
|
settings: record
|
||
|
|
server: record
|
||
|
|
ip_type: string
|
||
|
|
error_exit: bool
|
||
|
|
]: nothing -> string {
|
||
|
|
# Resolve template variables if present
|
||
|
|
let resolved_ip_type = (resolve_ip_template $server $ip_type)
|
||
|
|
let hostname = $server.hostname
|
||
|
|
let res = (dispatch_provider_function $server.provider "get_ip" $settings $hostname $resolved_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 all infrastructure prices at once (provider-agnostic) - Phase 2 optimization
|
||
|
|
# Returns: { hour: float, day: float, month: float, unit_info: string }
|
||
|
|
export def mw_get_all_infra_prices [
|
||
|
|
server: record
|
||
|
|
data: record
|
||
|
|
error_exit: bool
|
||
|
|
price_col?: string
|
||
|
|
]: nothing -> record {
|
||
|
|
# Try to call provider-specific batched implementation if available
|
||
|
|
let res = (dispatch_provider_function $server.provider "get_all_infra_prices" $server $data $error_exit $price_col)
|
||
|
|
|
||
|
|
if $res != null {
|
||
|
|
# Provider has batched implementation
|
||
|
|
$res
|
||
|
|
} else {
|
||
|
|
# Fallback to individual calls (for providers without batched implementation)
|
||
|
|
let hour = (mw_get_infra_price $server $data "hour" $error_exit $price_col)
|
||
|
|
{
|
||
|
|
hour: $hour,
|
||
|
|
day: (($hour * 24) | math round -p 4),
|
||
|
|
month: ((mw_get_infra_price $server $data "month" $error_exit $price_col) | math round -p 4),
|
||
|
|
unit_info: (mw_get_infra_price $server $data "unit" $error_exit $price_col)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# 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
|
||
|
|
}
|
||
|
|
|
||
|
|
# Create cache for server (provider-specific)
|
||
|
|
export def mw_create_cache [
|
||
|
|
settings: record
|
||
|
|
server: record
|
||
|
|
error_exit: bool
|
||
|
|
]: nothing -> nothing {
|
||
|
|
if not ($env.PROVIDER_REGISTRY_INITIALIZED? | default false) {
|
||
|
|
init-middleware | ignore
|
||
|
|
}
|
||
|
|
|
||
|
|
let provider_name = $server.provider
|
||
|
|
|
||
|
|
# Dynamic provider dispatch
|
||
|
|
let res = (dispatch_provider_function $provider_name "create_cache" $settings $server $error_exit)
|
||
|
|
|
||
|
|
if $res == null {
|
||
|
|
provider_undefined $server
|
||
|
|
if $error_exit {
|
||
|
|
log-error $"Failed to create cache for ($server.hostname)" "middleware"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get IP from cache (provider-specific)
|
||
|
|
# server: record with .hostname and .provider fields
|
||
|
|
export def mw_ip_from_cache [
|
||
|
|
settings: record
|
||
|
|
server: record
|
||
|
|
error_exit: bool
|
||
|
|
]: nothing -> string {
|
||
|
|
if not ($env.PROVIDER_REGISTRY_INITIALIZED? | default false) {
|
||
|
|
init-middleware | ignore
|
||
|
|
}
|
||
|
|
|
||
|
|
let provider_name = $server.provider
|
||
|
|
let hostname = $server.hostname
|
||
|
|
|
||
|
|
let res = (dispatch_provider_function $provider_name "ip_from_cache" $settings $hostname $error_exit)
|
||
|
|
|
||
|
|
if $res == null {
|
||
|
|
""
|
||
|
|
} else {
|
||
|
|
$res
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Modify server (provider-specific)
|
||
|
|
export def mw_modify_server [
|
||
|
|
settings: record
|
||
|
|
server: record
|
||
|
|
modifications: list
|
||
|
|
check: bool
|
||
|
|
]: nothing -> any {
|
||
|
|
if not ($env.PROVIDER_REGISTRY_INITIALIZED? | default false) {
|
||
|
|
init-middleware | ignore
|
||
|
|
}
|
||
|
|
|
||
|
|
let provider_name = $server.provider
|
||
|
|
|
||
|
|
# Dynamic provider dispatch
|
||
|
|
let res = (dispatch_provider_function $provider_name "modify_server" $settings $server $modifications $check)
|
||
|
|
|
||
|
|
if $res == null {
|
||
|
|
provider_undefined $server
|
||
|
|
return null
|
||
|
|
}
|
||
|
|
|
||
|
|
$res
|
||
|
|
}
|
||
|
|
|
||
|
|
# Post-create server hook (provider-specific)
|
||
|
|
export def mw_post_create_server [
|
||
|
|
settings: record
|
||
|
|
server: record
|
||
|
|
check: bool
|
||
|
|
]: nothing -> string {
|
||
|
|
if not ($env.PROVIDER_REGISTRY_INITIALIZED? | default false) {
|
||
|
|
init-middleware | ignore
|
||
|
|
}
|
||
|
|
|
||
|
|
let provider_name = $server.provider
|
||
|
|
|
||
|
|
# Dynamic provider dispatch
|
||
|
|
let res = (dispatch_provider_function $provider_name "post_create_server" $settings $server $check)
|
||
|
|
|
||
|
|
if $res == null {
|
||
|
|
provider_undefined $server
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
|
||
|
|
$res
|
||
|
|
}
|
||
|
|
|
||
|
|
# Enrich template context with provider-cached resources (provider-specific)
|
||
|
|
export def mw_enrich_template_context [
|
||
|
|
settings: record
|
||
|
|
server: record
|
||
|
|
context: record
|
||
|
|
]: nothing -> record {
|
||
|
|
if not ($env.PROVIDER_REGISTRY_INITIALIZED? | default false) {
|
||
|
|
init-middleware | ignore
|
||
|
|
}
|
||
|
|
|
||
|
|
let private_network = ($server.networking?.private_network? | default "")
|
||
|
|
let res = (dispatch_provider_function $server.provider "enrich_template_context" $settings $context $private_network)
|
||
|
|
|
||
|
|
if $res == null {
|
||
|
|
provider_undefined $server
|
||
|
|
return $context
|
||
|
|
}
|
||
|
|
|
||
|
|
$res
|
||
|
|
}
|
||
|
|
|
||
|
|
# Post-generate server hook (provider-specific)
|
||
|
|
export def mw_post_generate_server [
|
||
|
|
settings: record
|
||
|
|
server: record
|
||
|
|
check: bool
|
||
|
|
]: nothing -> string {
|
||
|
|
if not ($env.PROVIDER_REGISTRY_INITIALIZED? | default false) {
|
||
|
|
init-middleware | ignore
|
||
|
|
}
|
||
|
|
|
||
|
|
let provider_name = $server.provider
|
||
|
|
|
||
|
|
# Dynamic provider dispatch
|
||
|
|
let res = (dispatch_provider_function $provider_name "post_generate_server" $settings $server $check)
|
||
|
|
|
||
|
|
if $res == null {
|
||
|
|
provider_undefined $server
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
|
||
|
|
$res
|
||
|
|
}
|
||
|
|
|
||
|
|
# === DNS MIDDLEWARE FUNCTIONS ===
|
||
|
|
# Provider-agnostic DNS dispatch via dispatch_provider_function.
|
||
|
|
# DNS provider is resolved from zone config (separate from compute provider).
|
||
|
|
|
||
|
|
export def mw_dns_zone_get [
|
||
|
|
provider: string
|
||
|
|
zone: string
|
||
|
|
]: nothing -> record {
|
||
|
|
let res = (dispatch_provider_function $provider "dns_zone_get" $zone)
|
||
|
|
if $res == null { error make { msg: $"DNS zone_get failed for ($zone) via ($provider)" } }
|
||
|
|
$res
|
||
|
|
}
|
||
|
|
|
||
|
|
export def mw_dns_record_list [
|
||
|
|
provider: string
|
||
|
|
zone: string
|
||
|
|
]: nothing -> list {
|
||
|
|
let res = (dispatch_provider_function $provider "dns_record_list" $zone)
|
||
|
|
$res | default []
|
||
|
|
}
|
||
|
|
|
||
|
|
export def mw_dns_record_create [
|
||
|
|
provider: string
|
||
|
|
record: record
|
||
|
|
]: nothing -> record {
|
||
|
|
let res = (dispatch_provider_function $provider "dns_record_create" $record)
|
||
|
|
if $res == null { error make { msg: $"DNS record_create failed via ($provider)" } }
|
||
|
|
$res
|
||
|
|
}
|
||
|
|
|
||
|
|
export def mw_dns_record_update [
|
||
|
|
provider: string
|
||
|
|
id: string
|
||
|
|
record: record
|
||
|
|
]: nothing -> record {
|
||
|
|
let res = (dispatch_provider_function $provider "dns_record_update" $id $record)
|
||
|
|
if $res == null { error make { msg: $"DNS record_update ($id) failed via ($provider)" } }
|
||
|
|
$res
|
||
|
|
}
|
||
|
|
|
||
|
|
export def mw_dns_record_delete [
|
||
|
|
provider: string
|
||
|
|
id: string
|
||
|
|
zone: string
|
||
|
|
]: nothing -> nothing {
|
||
|
|
dispatch_provider_function $provider "dns_record_delete" $id $zone | ignore
|
||
|
|
}
|
||
|
|
|
||
|
|
export def mw_dns_record_upsert [
|
||
|
|
provider: string
|
||
|
|
record: record
|
||
|
|
]: nothing -> record {
|
||
|
|
let res = (dispatch_provider_function $provider "dns_record_upsert" $record)
|
||
|
|
if $res == null { error make { msg: $"DNS record_upsert failed via ($provider)" } }
|
||
|
|
$res
|
||
|
|
}
|
||
|
|
|
||
|
|
# === REGISTRAR MIDDLEWARE FUNCTIONS ===
|
||
|
|
# Provider-agnostic registrar dispatch via dispatch_provider_function.
|
||
|
|
# Manages domain ownership, nameserver delegation, and expiration — separate from DNS record management.
|
||
|
|
# filter.domains scopes results to declared infra domains; empty filter = admin mode (all domains).
|
||
|
|
|
||
|
|
export def mw_registrar_list_domains [
|
||
|
|
provider: string
|
||
|
|
filter: record
|
||
|
|
]: nothing -> list<record> {
|
||
|
|
let res = (dispatch_provider_function $provider "registrar_list_domains" $filter)
|
||
|
|
$res | default []
|
||
|
|
}
|
||
|
|
|
||
|
|
export def mw_registrar_domain_info [
|
||
|
|
provider: string
|
||
|
|
domain: string
|
||
|
|
]: nothing -> record {
|
||
|
|
let res = (dispatch_provider_function $provider "registrar_domain_info" $domain)
|
||
|
|
if $res == null { error make { msg: $"Registrar domain_info failed for ($domain) via ($provider)" } }
|
||
|
|
$res
|
||
|
|
}
|
||
|
|
|
||
|
|
export def mw_registrar_get_nameservers [
|
||
|
|
provider: string
|
||
|
|
domain: string
|
||
|
|
]: nothing -> list<string> {
|
||
|
|
let res = (dispatch_provider_function $provider "registrar_get_nameservers" $domain)
|
||
|
|
$res | default []
|
||
|
|
}
|
||
|
|
|
||
|
|
export def mw_registrar_set_nameservers [
|
||
|
|
provider: string
|
||
|
|
domain: string
|
||
|
|
ns: list<string>
|
||
|
|
]: nothing -> bool {
|
||
|
|
let res = (dispatch_provider_function $provider "registrar_set_nameservers" $domain $ns)
|
||
|
|
$res | default false
|
||
|
|
}
|
||
|
|
|
||
|
|
export def mw_registrar_set_auto_renew [
|
||
|
|
provider: string
|
||
|
|
domain: string
|
||
|
|
enable: bool
|
||
|
|
]: nothing -> bool {
|
||
|
|
let res = (dispatch_provider_function $provider "registrar_set_auto_renew" $domain $enable)
|
||
|
|
$res | default false
|
||
|
|
}
|
||
|
|
|
||
|
|
# Initialize middleware when loaded
|
||
|
|
export-env {
|
||
|
|
# This will be set when middleware functions are first called
|
||
|
|
}
|