# Provider Configuration Module # Manages infrastructure provider setup and configuration (UpCloud, AWS, Hetzner) # Follows Nushell guidelines: explicit types, single purpose, no try-catch use ./mod.nu * use ./validation.nu * # ============================================================================ # PROVIDER VALIDATION # ============================================================================ # Check if provider is available in current workspace export def is-provider-available [ provider_name: string workspace_path: string ]: nothing -> bool { let provider_config = $"($workspace_path)/config/providers/($provider_name).toml" ($provider_config | path exists) } # Get list of available providers export def get-available-providers [ config_base: string ]: nothing -> list { let providers_dir = $"($config_base)/providers" if not ($providers_dir | path exists) { return [] } let result = (do { ls -la $providers_dir | where name =~ r'\.toml$' | get name | str replace ".toml" "" } | complete) if ($result.exit_code == 0) { $result.stdout | split row "\n" | where { |x| ($x | str length) > 0 } } else { [] } } # ============================================================================ # PROVIDER CONFIGURATION FILES # ============================================================================ # Create UpCloud provider configuration export def create-upcloud-config [ config_base: string ]: nothing -> bool { let provider_config = $"($config_base)/providers/upcloud.toml" let upcloud_config = { api_url: "https://api.upcloud.com/1.3" interface: "API" credentials_source: "rustyvault://system/providers/upcloud" timeout_seconds: 30 zones: ["de-fra1", "nl-ams1", "sg-sin1", "us-nyc1", "us-sfo1", "gb-lnd1"] } save-config-toml $provider_config $upcloud_config } # Create AWS provider configuration export def create-aws-config [ config_base: string ]: nothing -> bool { let provider_config = $"($config_base)/providers/aws.toml" let aws_config = { region: "us-east-1" credentials_source: "rustyvault://system/providers/aws" timeout_seconds: 30 regions: ["us-east-1", "us-west-2", "eu-west-1", "ap-southeast-1"] } save-config-toml $provider_config $aws_config } # Create Hetzner provider configuration export def create-hetzner-config [ config_base: string ]: nothing -> bool { let provider_config = $"($config_base)/providers/hetzner.toml" let hetzner_config = { api_url: "https://api.hetzner.cloud/v1" credentials_source: "rustyvault://system/providers/hetzner" timeout_seconds: 30 locations: ["fsn1", "nbg1", "ash", "hel1", "sin"] } save-config-toml $provider_config $hetzner_config } # Create local provider configuration export def create-local-config [ config_base: string ]: nothing -> bool { let provider_config = $"($config_base)/providers/local.toml" let local_config = { base_path: "/tmp/provisioning-local" timeout_seconds: 10 } save-config-toml $provider_config $local_config } # ============================================================================ # PROVIDER SETUP # ============================================================================ # Setup provider configuration export def setup-provider [ provider_name: string config_base: string = "" --interactive = false ]: nothing -> record { let base = (if ($config_base == "") { (get-config-base-path) } else { $config_base }) # Validate provider name let validation = (validate-provider-name $provider_name) if not $validation.valid { print-setup-error $validation.error return { success: false error: $validation.error } } print-setup-header $"Setting up ($provider_name) Provider" print "" # Create provider configuration let creation_result = (match $provider_name { "upcloud" => { create-upcloud-config $base } "aws" => { create-aws-config $base } "hetzner" => { create-hetzner-config $base } "local" => { create-local-config $base } _ => { false } }) if not $creation_result { print-setup-error $"Failed to create ($provider_name) provider configuration" return { success: false error: $"Failed to create provider configuration for ($provider_name)" } } print-setup-success $"($provider_name) provider configuration created" print-setup-info "Credentials will be loaded from RustyVault at runtime" let timestamp_value = (get-timestamp-iso8601) { success: true provider: $provider_name config_path: $"($base)/providers/($provider_name).toml" timestamp: $timestamp_value } } # Setup all selected providers export def setup-providers [ providers: list config_base: string = "" ]: nothing -> record { let base = (if ($config_base == "") { (get-config-base-path) } else { $config_base }) print-setup-header "Setting up Providers" print "" mut results = [] mut errors = [] for provider in $providers { print-setup-info $"Configuring ($provider)..." let result = (setup-provider $provider $base) if $result.success { $results = ($results | append $result) print-setup-success $"($provider) configured" } else { $errors = ($errors | append $result.error) print-setup-warning $result.error } } print "" let success_status = ($errors | length) == 0 let provider_list = ($results | map { |r| $r.provider }) let configured_count = ($results | length) let error_count = ($errors | length) let timestamp_value = (get-timestamp-iso8601) { success: $success_status providers: $provider_list configured_count: $configured_count error_count: $error_count errors: $errors timestamp: $timestamp_value } } # ============================================================================ # PROVIDER CREDENTIALS (References to RustyVault) # ============================================================================ # Get provider credentials reference export def get-provider-credentials-reference [ provider_name: string workspace_name: string = "system" ]: nothing -> string { $"rustyvault://($workspace_name)/providers/($provider_name)" } # Validate credentials reference format export def validate-credentials-reference [ credentials_source: string ]: nothing -> record { let is_valid = ( ($credentials_source | str starts-with "rustyvault://") or ($credentials_source | str starts-with "vault://") or ($credentials_source | str starts-with "kms://") ) let error_message = (if not $is_valid { "Credentials source must start with rustyvault://, vault://, or kms://" } else { null }) { value: $credentials_source valid: $is_valid error: $error_message } } # ============================================================================ # PROVIDER INFORMATION # ============================================================================ # Print provider setup instructions export def print-provider-setup-instructions []: nothing -> nothing { print "" print "╔═══════════════════════════════════════════════════════════════╗" print "║ PROVIDER SETUP INSTRUCTIONS ║" print "╚═══════════════════════════════════════════════════════════════╝" print "" print "📋 PROVIDER CREDENTIALS" print "─────────────────────────────────────────────────────────────" print "" print "Provider credentials are stored SECURELY in RustyVault with:" print " ✅ Encryption at rest (AES-256)" print " ✅ Dynamic secrets with TTL" print " ✅ Automatic rotation policies" print " ✅ Audit logging of all access" print "" print "UpCloud Provider" print "─────────────────────────────────────────────────────────────" print "To setup UpCloud credentials in RustyVault:" print "" print " 1. Obtain API credentials from https://hub.upcloud.com/account" print " 2. Store in RustyVault:" print " provisioning secrets store upcloud-creds \\" print " --username \\" print " --password " print "" print " 3. Credentials available as: rustyvault://system/providers/upcloud" print "" print "AWS Provider" print "─────────────────────────────────────────────────────────────" print "To setup AWS credentials in RustyVault:" print "" print " 1. Create IAM user with programmatic access" print " 2. Store in RustyVault:" print " provisioning secrets store aws-creds \\" print " --access-key-id \\" print " --secret-access-key \\" print " --ttl 3600" print "" print " 3. Credentials available as: rustyvault://system/providers/aws" print "" print "Hetzner Provider" print "─────────────────────────────────────────────────────────────" print "To setup Hetzner credentials in RustyVault:" print "" print " 1. Create API token from https://console.hetzner.cloud" print " 2. Store in RustyVault:" print " provisioning secrets store hetzner-creds \\" print " --api-token " print "" print " 3. Credentials available as: rustyvault://system/providers/hetzner" print "" print "Local Provider" print "─────────────────────────────────────────────────────────────" print "Local provider requires no credentials - perfect for testing!" print "" print "═══════════════════════════════════════════════════════════════" print "" } # Print available providers export def print-available-providers [ config_base: string = "" ]: nothing -> nothing { let base = (if ($config_base == "") { (get-config-base-path) } else { $config_base }) let available = (get-available-providers $base) print "" print "Configured Providers:" print "─────────────────────────────────────────────────────────────" if ($available | length) == 0 { print " (No providers configured yet)" } else { for provider in $available { let config_path = $"($base)/providers/($provider).toml" print $" ✅ ($provider)" print $" Path: ($config_path)" } } print "" } # Get provider information export def get-provider-info [ provider_name: string config_base: string = "" ]: nothing -> record { let base = (if ($config_base == "") { (get-config-base-path) } else { $config_base }) let config_path = $"($base)/providers/($provider_name).toml" let config = if ($config_path | path exists) { load-config-toml $config_path } else { {} } let exists_status = ($config_path | path exists) { provider: $provider_name exists: $exists_status config_path: $config_path configuration: $config } }