provisioning-catalog/providers/hetzner/provider.nu

283 lines
10 KiB
Text
Raw Permalink Normal View History

# Hetzner Cloud Provider Interface Implementation
# Implements all 25 required provider interface functions
use ./nulib/hetzner/mod.nu *
# ============================================================================
# Provider Metadata Function (REQUIRED)
# ============================================================================
export def get-provider-metadata []: nothing -> record {
{
name: "hetzner"
version: "1.0.0"
type: "cloud"
description: "Hetzner Cloud provider for European cloud infrastructure"
capabilities: {
server_management: true
network_management: true
storage_management: true
load_balancer: true
firewall: true
floating_ip: true
volume_management: true
ssh_management: true
placement_groups: true
backups: true
snapshots: true
managed_databases: false
kubernetes: false
}
api: {
type: "rest"
base_url: "https://api.hetzner.cloud/v1"
auth_method: "bearer_token"
documentation: "https://docs.hetzner.cloud/"
version: "v1"
}
cli: {
tool: "hcloud"
version: "1.44.0"
install_url: "https://github.com/hetznercloud/cli"
fallback: true
}
limits: {
max_servers: 50
max_volumes: 100
max_networks: 10
max_floating_ips: 20
}
regions: [
{code: "fsn1", name: "Falkenstein", country: "Germany"}
{code: "nbg1", name: "Nuremberg", country: "Germany"}
{code: "hel1", name: "Helsinki", country: "Finland"}
{code: "ash", name: "Ashburn", country: "USA"}
{code: "hil", name: "Hillsboro", country: "USA"}
]
}
}
# ============================================================================
# Server Query Functions (4 functions)
# ============================================================================
export def query_servers [find?: string, cols?: string]: nothing -> list {
let str_find = if $find != null { $find } else { "" }
let str_cols = if $cols != null { $cols } else { "" }
hetzner_query_servers $str_find $str_cols
}
export def server_info [server: string, check: bool = false, find?: string, cols?: string]: nothing -> record {
let str_find = if $find != null { $find } else { "" }
let str_cols = if $cols != null { $cols } else { "" }
hetzner_server_info $server $check $str_find $str_cols
}
export def server_exists [server: string, error_exit: bool = false]: nothing -> bool {
hetzner_server_exists $server $error_exit
}
export def server_is_running [server: string, error_exit: bool = false]: nothing -> bool {
hetzner_server_is_running $server $error_exit
}
# ============================================================================
# Server Lifecycle Functions (6 functions)
# ============================================================================
export def check_server_requirements [settings: record, server: record, check: bool = false]: nothing -> bool {
hetzner_check_server_requirements $settings $server $check
}
export def create_server [settings: record, server: record, check: bool = false, wait: bool = true]: nothing -> record {
hetzner_create_server $settings $server $check $wait
}
export def delete_server [settings: record, server: record, keep_storage: bool = true, error_exit: bool = true]: nothing -> nothing {
hetzner_delete_server $settings $server.hostname $keep_storage $error_exit
}
export def delete_server_storage [settings: record, server: record, error_exit: bool = true]: nothing -> nothing {
hetzner_delete_server_storage $settings $server.hostname $error_exit
}
export def post_create_server [settings: record, server: record, check: bool = false]: nothing -> nothing {
hetzner_post_create_server $settings $server $check
}
export def modify_server [settings: record, server: string, new_values: record, error_exit: bool = true]: nothing -> record {
hetzner_modify_server $settings $server $new_values $error_exit
}
# ============================================================================
# Server State Functions (4 functions)
# ============================================================================
export def server_state [server: string, new_state: string, error_exit: bool = true, wait: bool = true, settings: record = {}]: nothing -> record {
hetzner_server_state $server $new_state $error_exit $wait $settings
}
# Power off and block until status=="off" (or timeout). Required by callers
# that need a deterministic post-state — e.g. fleet daemon's AlwaysOnResize
# strategy chains power_off → change_type → power_on and depends on the
# guarantee that the server is genuinely off before issuing change_type.
export def power_off [server: string, timeout_sec: int = 30, poll_ms: int = 500]: nothing -> record {
hetzner_power_off_wait $server $timeout_sec $poll_ms
}
# Power on (fire-and-forget, idempotent).
export def power_on [server: string]: nothing -> record {
hetzner_power_on $server
}
# Full resize flow: power_off (wait) → change_type (upgrade_disk=!keep_disk) →
# power_on. keep_disk defaults to true (preserve disk allocation, AlwaysOnResize
# semantics).
export def resize_server [server: string, target_type: string, keep_disk: bool = true, settings: record = {}]: nothing -> record {
hetzner_resize_server $server $target_type $keep_disk $settings
}
# ============================================================================
# Network Operations (2 functions)
# ============================================================================
export def get_ip [settings: record, server: string, ip_type: string = "ipv4", error_exit: bool = true]: nothing -> string {
hetzner_get_ip $settings $server $ip_type $error_exit
}
export def servers_ips [settings: record, data: any, prov: string = "hetzner", serverpos: int = 0]: nothing -> list {
hetzner_servers_ips $settings $data $prov $serverpos
}
# ============================================================================
# Infrastructure Operations (5 functions)
# ============================================================================
export def load_infra_servers_info [settings: record, server: record, error_exit: bool = true]: nothing -> record {
hetzner_server_info $server.hostname false "" ""
}
export def load_infra_storages_info [settings: record, server: record, error_exit: bool = true]: nothing -> list {
let servers = query_servers "" ""
let server_data = $servers | where {|s| $s.name == $server.hostname} | first
if ("volumes" in $server_data) {
$server_data.volumes
} else {
[]
}
}
export def get_infra_storage [server: record, settings: record, cloud_data: any, error_exit: bool = true]: nothing -> record {
if ("volumes" in $cloud_data) and (not ($cloud_data.volumes | is-empty)) {
$cloud_data.volumes | first
} else {
{}
}
}
export def get_infra_item [server: record, settings: record, cloud_data: any, error_exit: bool = true]: nothing -> record {
if ($cloud_data | describe) == "record" {
$cloud_data
} else {
{}
}
}
export def get_infra_price [server: record, data: any, key: string, error_exit: bool = true, price_col: string = "total_monthly_gross"]: nothing -> float {
let cost = hetzner_calculate_server_cost $server
if ("error" in $cost) {
if $error_exit {
throw-error $"Could not calculate price: ($cost.message)"
}
return 0
}
if ($price_col in $cost) {
$cost | get $price_col
} else {
0
}
}
# ============================================================================
# Cache Operations (5 functions)
# ============================================================================
export def start_cache_info [settings: record, server: string]: nothing -> nothing {
hetzner_start_cache_info $settings $server
}
export def create_cache [settings: record, server: string, error_exit: bool = true]: nothing -> nothing {
hetzner_create_cache $settings $server $error_exit
}
export def read_cache [settings: record, server: string, error_exit: bool = true]: nothing -> record {
hetzner_read_cache $settings $server $error_exit
}
export def clean_cache [settings: record, server: string, error_exit: bool = true]: nothing -> nothing {
hetzner_clean_cache $settings $server $error_exit
}
export def ip_from_cache [settings: record, server: string, error_exit: bool = true]: nothing -> string {
hetzner_ip_from_cache $settings $server $error_exit
}
export def enrich_template_context [settings: record, context: record, private_network: string]: nothing -> record {
hetzner_enrich_template_context $settings $context $private_network
}
# ============================================================================
# Display/Message Function (1 function)
# ============================================================================
export def on_prov_server [server: record]: nothing -> string {
let location = if ("location" in $server) {$server.location} else {"unknown"}
let type = if ("server_type" in $server) {$server.server_type} else {"unknown"}
$"Hetzner Cloud ($location) - ($type)"
}
# ============================================================================
# Helper Functions (Optional, but useful)
# ============================================================================
export def hetzner_version []: nothing -> string {
"1.0.0"
}
export def hetzner_info []: nothing -> record {
get-provider-metadata
}
export def hetzner_supported_regions []: nothing -> list {
let meta = get-provider-metadata
$meta.regions | map {|r| $r.code}
}
export def hetzner_supported_server_types []: nothing -> list {
let types = hetzner_api_list_server_types
$types | each {|t| $t.name}
}
export def hetzner_list_locations []: nothing -> list {
hetzner_api_list_locations
}
export def hetzner_get_account_info []: nothing -> record {
{
provider: "hetzner"
version: (hetzner_version)
status: "active"
interface: (hetzner_interface)
timestamp: (now)
}
}