282 lines
10 KiB
Text
282 lines
10 KiB
Text
# 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)
|
|
}
|
|
}
|