provisioning-core/nulib/domain/config/schema_validator.nu

331 lines
11 KiB
Text
Raw Normal View History

# 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"
}
}
}