330 lines
11 KiB
Text
330 lines
11 KiB
Text
# 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"
|
|
}
|
|
}
|
|
}
|