# Schema Validator # Handles validation of infrastructure configurations against defined schemas # Error handling: Guard patterns (no try-catch for field access) # Server configuration schema validation export def validate_server_schema [config: record] { mut issues = [] # Required fields for server configuration let required_fields = [ "hostname" "provider" "zone" "plan" ] for field in $required_fields { # Guard: Check if field exists in config using columns let field_exists = ($field in ($config | columns)) let field_value = if $field_exists { $config | get $field } else { null } if ($field_value | is-empty) { $issues = ($issues | append { field: $field message: $"Required field '($field)' is missing or empty" severity: "error" }) } } # Validate specific field formats # Guard: Check if hostname field exists if ("hostname" in ($config | columns)) { let hostname = ($config | get hostname) if not ($hostname =~ '^[a-z0-9][a-z0-9\-]*[a-z0-9]$') { $issues = ($issues | append { field: "hostname" message: "Hostname must contain only lowercase letters, numbers, and hyphens" severity: "warning" current_value: $hostname }) } } # Validate provider-specific requirements # Guard: Check if provider field exists if ("provider" in ($config | columns)) { let provider = ($config | get provider) let provider_validation = (validate_provider_config $provider $config) $issues = ($issues | append $provider_validation.issues) } # Validate network configuration # Guard: Check if network_private_ip field exists if ("network_private_ip" in ($config | columns)) { let ip = ($config | get network_private_ip) let ip_validation = (validate_ip_address $ip) if not $ip_validation.valid { $issues = ($issues | append { field: "network_private_ip" message: $ip_validation.message severity: "error" current_value: $ip }) } } { valid: (($issues | where severity == "error" | length) == 0) issues: $issues } } # Provider-specific configuration validation export def validate_provider_config [provider: string, config: record] { mut issues = [] match $provider { "upcloud" => { # UpCloud specific validations let required_upcloud_fields = ["ssh_key_path", "storage_os"] for field in $required_upcloud_fields { # Guard: Check if field exists in config if not ($field in ($config | columns)) { $issues = ($issues | append { field: $field message: $"UpCloud provider requires '($field)' field" severity: "error" }) } } # Validate UpCloud zones let valid_zones = ["es-mad1", "fi-hel1", "fi-hel2", "nl-ams1", "sg-sin1", "uk-lon1", "us-chi1", "us-nyc1", "de-fra1"] # Guard: Check if zone field exists let zone = if ("zone" in ($config | columns)) { $config | get zone } else { null } if ($zone | is-not-empty) and ($zone not-in $valid_zones) { $issues = ($issues | append { field: "zone" message: $"Invalid UpCloud zone: ($zone)" severity: "error" current_value: $zone suggested_values: $valid_zones }) } } "aws" => { # AWS specific validations let required_aws_fields = ["instance_type", "ami_id"] for field in $required_aws_fields { # Guard: Check if field exists in config if not ($field in ($config | columns)) { $issues = ($issues | append { field: $field message: $"AWS provider requires '($field)' field" severity: "error" }) } } } "local" => { # Local provider specific validations # Generally more lenient } _ => { $issues = ($issues | append { field: "provider" message: $"Unknown provider: ($provider)" severity: "error" current_value: $provider suggested_values: ["upcloud", "aws", "local"] }) } } { issues: $issues } } # Network configuration validation export def validate_network_config [config: record] { mut issues = [] # Validate CIDR blocks # Guard: Check if priv_cidr_block field exists if ("priv_cidr_block" in ($config | columns)) { let cidr = ($config | get priv_cidr_block) let cidr_validation = (validate_cidr_block $cidr) if not $cidr_validation.valid { $issues = ($issues | append { field: "priv_cidr_block" message: $cidr_validation.message severity: "error" current_value: $cidr }) } } # Check for IP conflicts # Guard: Check if both fields exist in config if ("network_private_ip" in ($config | columns)) and ("priv_cidr_block" in ($config | columns)) { let ip = ($config | get network_private_ip) let cidr = ($config | get priv_cidr_block) if not (ip_in_cidr $ip $cidr) { $issues = ($issues | append { field: "network_private_ip" message: $"IP ($ip) is not within CIDR block ($cidr)" severity: "error" }) } } { valid: (($issues | where severity == "error" | length) == 0) issues: $issues } } # TaskServ configuration validation export def validate_taskserv_schema [taskserv: record] { mut issues = [] let required_fields = ["name", "install_mode"] for field in $required_fields { # Guard: Check if field exists in taskserv if not ($field in ($taskserv | columns)) { $issues = ($issues | append { field: $field message: $"Required taskserv field '($field)' is missing" severity: "error" }) } } # Validate install mode let valid_install_modes = ["library", "container", "binary"] # Guard: Check if install_mode field exists let install_mode = if ("install_mode" in ($taskserv | columns)) { $taskserv | get install_mode } else { null } if ($install_mode | is-not-empty) and ($install_mode not-in $valid_install_modes) { $issues = ($issues | append { field: "install_mode" message: $"Invalid install_mode: ($install_mode)" severity: "error" current_value: $install_mode suggested_values: $valid_install_modes }) } # Validate taskserv name exists # Guard: Check if name field exists let taskserv_name = if ("name" in ($taskserv | columns)) { $taskserv | get name } else { null } if ($taskserv_name | is-not-empty) { let taskserv_exists = (taskserv_definition_exists $taskserv_name) if not $taskserv_exists { $issues = ($issues | append { field: "name" message: $"TaskServ definition not found: ($taskserv_name)" severity: "warning" current_value: $taskserv_name }) } } { valid: (($issues | where severity == "error" | length) == 0) issues: $issues } } # Helper validation functions export def validate_ip_address [ip: string] { # Basic IP address validation (IPv4) if ($ip =~ '^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$') { let parts = ($ip | split row ".") let valid_parts = ($parts | all {|part| let num = ($part | into int) $num >= 0 and $num <= 255 }) if $valid_parts { { valid: true, message: "" } } else { { valid: false, message: "IP address octets must be between 0 and 255" } } } else { { valid: false, message: "Invalid IP address format" } } } export def validate_cidr_block [cidr: string] { if ($cidr =~ '^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})$') { let parts = ($cidr | split row "/") let ip_part = ($parts | get 0) let prefix = ($parts | get 1 | into int) let ip_valid = (validate_ip_address $ip_part) if not $ip_valid.valid { return $ip_valid } if $prefix >= 0 and $prefix <= 32 { { valid: true, message: "" } } else { { valid: false, message: "CIDR prefix must be between 0 and 32" } } } else { { valid: false, message: "Invalid CIDR block format (should be x.x.x.x/y)" } } } export def ip_in_cidr [ip: string, cidr: string] { # Simplified IP in CIDR check # This is a basic implementation - a more robust version would use proper IP arithmetic let cidr_parts = ($cidr | split row "/") let network = ($cidr_parts | get 0) let prefix = ($cidr_parts | get 1 | into int) # For basic validation, check if IP starts with the same network portion # This is simplified and should be enhanced for production use if $prefix >= 24 { let network_base = ($network | split row "." | take 3 | str join ".") let ip_base = ($ip | split row "." | take 3 | str join ".") $network_base == $ip_base } else { # For smaller networks, more complex logic would be needed true # Simplified for now } } export def taskserv_definition_exists [name: string] { # Check if taskserv definition exists in the system let taskserv_path = $"taskservs/($name)" ($taskserv_path | path exists) } # Schema definitions for different resource types export def get_server_schema [] { { required_fields: ["hostname", "provider", "zone", "plan"] optional_fields: [ "title", "labels", "ssh_key_path", "storage_os", "network_private_ip", "priv_cidr_block", "time_zone", "taskservs", "storages" ] field_types: { hostname: "string" provider: "string" zone: "string" plan: "string" network_private_ip: "ip_address" priv_cidr_block: "cidr" taskservs: "list" } } } export def get_taskserv_schema [] { { required_fields: ["name", "install_mode"] optional_fields: ["profile", "target_save_path"] field_types: { name: "string" install_mode: "string" profile: "string" target_save_path: "string" } } }