356 lines
12 KiB
Text
356 lines
12 KiB
Text
# Module: Configuration Validator
|
|
# Purpose: Validates configuration structure, paths, data types, semantic rules, and file existence.
|
|
# Dependencies: loader_core for get-config-value
|
|
|
|
# Configuration Validation Functions
|
|
# Validates configuration structure, paths, data types, semantic rules, and files
|
|
|
|
# Validate configuration structure - checks required sections exist
|
|
export def validate-config-structure [
|
|
config: record
|
|
] {
|
|
let required_sections = ["core", "paths", "debug", "sops"]
|
|
mut errors = []
|
|
mut warnings = []
|
|
|
|
for section in $required_sections {
|
|
let section_value = ($config | get -o $section | default null)
|
|
if ($section_value | is-empty) {
|
|
$errors = ($errors | append {
|
|
type: "missing_section",
|
|
severity: "error",
|
|
section: $section,
|
|
message: $"Missing required configuration section: ($section)"
|
|
})
|
|
}
|
|
}
|
|
|
|
{
|
|
valid: (($errors | length) == 0),
|
|
errors: $errors,
|
|
warnings: $warnings
|
|
}
|
|
}
|
|
|
|
# Validate path values - checks paths exist and are absolute
|
|
export def validate-path-values [
|
|
config: record
|
|
] {
|
|
let required_paths = ["base", "providers", "taskservs", "clusters"]
|
|
mut errors = []
|
|
mut warnings = []
|
|
|
|
let paths = ($config | get -o paths | default {})
|
|
|
|
for path_name in $required_paths {
|
|
let path_value = ($paths | get -o $path_name | default null)
|
|
|
|
if ($path_value | is-empty) {
|
|
$errors = ($errors | append {
|
|
type: "missing_path",
|
|
severity: "error",
|
|
path: $path_name,
|
|
message: $"Missing required path: paths.($path_name)"
|
|
})
|
|
} else {
|
|
# Check if path is absolute
|
|
if not ($path_value | str starts-with "/") {
|
|
$warnings = ($warnings | append {
|
|
type: "relative_path",
|
|
severity: "warning",
|
|
path: $path_name,
|
|
value: $path_value,
|
|
message: $"Path paths.($path_name) should be absolute, got: ($path_value)"
|
|
})
|
|
}
|
|
|
|
# Check if base path exists (critical for system operation)
|
|
if $path_name == "base" {
|
|
if not ($path_value | path exists) {
|
|
$errors = ($errors | append {
|
|
type: "path_not_exists",
|
|
severity: "error",
|
|
path: $path_name,
|
|
value: $path_value,
|
|
message: $"Base path does not exist: ($path_value)"
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
valid: (($errors | length) == 0),
|
|
errors: $errors,
|
|
warnings: $warnings
|
|
}
|
|
}
|
|
|
|
# Validate data types - checks configuration values have correct types
|
|
export def validate-data-types [
|
|
config: record
|
|
] {
|
|
mut errors = []
|
|
mut warnings = []
|
|
|
|
# Validate core.version follows semantic versioning pattern
|
|
let core_version = ($config | get -o core.version | default null)
|
|
if ($core_version | is-not-empty) {
|
|
let version_pattern = "^\\d+\\.\\d+\\.\\d+(-.+)?$"
|
|
let version_parts = ($core_version | split row ".")
|
|
if (($version_parts | length) < 3) {
|
|
$errors = ($errors | append {
|
|
type: "invalid_version",
|
|
severity: "error",
|
|
field: "core.version",
|
|
value: $core_version,
|
|
message: $"core.version must follow semantic versioning format, got: ($core_version)"
|
|
})
|
|
}
|
|
}
|
|
|
|
# Validate debug.enabled is boolean
|
|
let debug_enabled = ($config | get -o debug.enabled | default null)
|
|
if ($debug_enabled | is-not-empty) {
|
|
if (($debug_enabled | describe) != "bool") {
|
|
$errors = ($errors | append {
|
|
type: "invalid_type",
|
|
severity: "error",
|
|
field: "debug.enabled",
|
|
value: $debug_enabled,
|
|
expected: "bool",
|
|
actual: ($debug_enabled | describe),
|
|
message: $"debug.enabled must be boolean, got: ($debug_enabled | describe)"
|
|
})
|
|
}
|
|
}
|
|
|
|
# Validate debug.metadata is boolean
|
|
let debug_metadata = ($config | get -o debug.metadata | default null)
|
|
if ($debug_metadata | is-not-empty) {
|
|
if (($debug_metadata | describe) != "bool") {
|
|
$errors = ($errors | append {
|
|
type: "invalid_type",
|
|
severity: "error",
|
|
field: "debug.metadata",
|
|
value: $debug_metadata,
|
|
expected: "bool",
|
|
actual: ($debug_metadata | describe),
|
|
message: $"debug.metadata must be boolean, got: ($debug_metadata | describe)"
|
|
})
|
|
}
|
|
}
|
|
|
|
# Validate sops.use_sops is boolean
|
|
let sops_use = ($config | get -o sops.use_sops | default null)
|
|
if ($sops_use | is-not-empty) {
|
|
if (($sops_use | describe) != "bool") {
|
|
$errors = ($errors | append {
|
|
type: "invalid_type",
|
|
severity: "error",
|
|
field: "sops.use_sops",
|
|
value: $sops_use,
|
|
expected: "bool",
|
|
actual: ($sops_use | describe),
|
|
message: $"sops.use_sops must be boolean, got: ($sops_use | describe)"
|
|
})
|
|
}
|
|
}
|
|
|
|
{
|
|
valid: (($errors | length) == 0),
|
|
errors: $errors,
|
|
warnings: $warnings
|
|
}
|
|
}
|
|
|
|
# Validate semantic rules - business logic validation
|
|
export def validate-semantic-rules [
|
|
config: record
|
|
] {
|
|
mut errors = []
|
|
mut warnings = []
|
|
|
|
# Validate provider configuration
|
|
let providers = ($config | get -o providers | default {})
|
|
let default_provider = ($providers | get -o default | default null)
|
|
|
|
if ($default_provider | is-not-empty) {
|
|
let valid_providers = ["aws", "upcloud", "local"]
|
|
if not ($default_provider in $valid_providers) {
|
|
$errors = ($errors | append {
|
|
type: "invalid_provider",
|
|
severity: "error",
|
|
field: "providers.default",
|
|
value: $default_provider,
|
|
valid_options: $valid_providers,
|
|
message: $"Invalid default provider: ($default_provider). Valid options: ($valid_providers | str join ', ')"
|
|
})
|
|
}
|
|
}
|
|
|
|
# Validate log level
|
|
let log_level = ($config | get -o debug.log_level | default null)
|
|
if ($log_level | is-not-empty) {
|
|
let valid_levels = ["trace", "debug", "info", "warn", "error"]
|
|
if not ($log_level in $valid_levels) {
|
|
$warnings = ($warnings | append {
|
|
type: "invalid_log_level",
|
|
severity: "warning",
|
|
field: "debug.log_level",
|
|
value: $log_level,
|
|
valid_options: $valid_levels,
|
|
message: $"Invalid log level: ($log_level). Valid options: ($valid_levels | str join ', ')"
|
|
})
|
|
}
|
|
}
|
|
|
|
# Validate output format
|
|
let output_format = ($config | get -o output.format | default null)
|
|
if ($output_format | is-not-empty) {
|
|
let valid_formats = ["json", "yaml", "toml", "text"]
|
|
if not ($output_format in $valid_formats) {
|
|
$warnings = ($warnings | append {
|
|
type: "invalid_output_format",
|
|
severity: "warning",
|
|
field: "output.format",
|
|
value: $output_format,
|
|
valid_options: $valid_formats,
|
|
message: $"Invalid output format: ($output_format). Valid options: ($valid_formats | str join ', ')"
|
|
})
|
|
}
|
|
}
|
|
|
|
{
|
|
valid: (($errors | length) == 0),
|
|
errors: $errors,
|
|
warnings: $warnings
|
|
}
|
|
}
|
|
|
|
# Validate file existence - checks referenced files exist
|
|
export def validate-file-existence [
|
|
config: record
|
|
] {
|
|
mut errors = []
|
|
mut warnings = []
|
|
|
|
# Check SOPS configuration file
|
|
let sops_config = ($config | get -o sops.config_path | default null)
|
|
if ($sops_config | is-not-empty) {
|
|
if not ($sops_config | path exists) {
|
|
$warnings = ($warnings | append {
|
|
type: "missing_sops_config",
|
|
severity: "warning",
|
|
field: "sops.config_path",
|
|
value: $sops_config,
|
|
message: $"SOPS config file not found: ($sops_config)"
|
|
})
|
|
}
|
|
}
|
|
|
|
# Check SOPS key files
|
|
let key_paths = ($config | get -o sops.key_search_paths | default [])
|
|
mut found_key = false
|
|
|
|
for key_path in $key_paths {
|
|
let expanded_path = ($key_path | str replace "~" $env.HOME)
|
|
if ($expanded_path | path exists) {
|
|
$found_key = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if not $found_key and ($key_paths | length) > 0 {
|
|
$warnings = ($warnings | append {
|
|
type: "missing_sops_keys",
|
|
severity: "warning",
|
|
field: "sops.key_search_paths",
|
|
value: $key_paths,
|
|
message: $"No SOPS key files found in search paths: ($key_paths | str join ', ')"
|
|
})
|
|
}
|
|
|
|
# Check critical configuration files
|
|
let settings_file = ($config | get -o paths.files.settings | default null)
|
|
if ($settings_file | is-not-empty) {
|
|
if not ($settings_file | path exists) {
|
|
$errors = ($errors | append {
|
|
type: "missing_settings_file",
|
|
severity: "error",
|
|
field: "paths.files.settings",
|
|
value: $settings_file,
|
|
message: $"Settings file not found: ($settings_file)"
|
|
})
|
|
}
|
|
}
|
|
|
|
{
|
|
valid: (($errors | length) == 0),
|
|
errors: $errors,
|
|
warnings: $warnings
|
|
}
|
|
}
|
|
|
|
# Enhanced main validation function
|
|
export def validate-config [
|
|
config: record
|
|
--detailed = false # Show detailed validation results
|
|
--strict = false # Treat warnings as errors
|
|
] {
|
|
# Run all validation checks
|
|
let structure_result = (validate-config-structure $config)
|
|
let paths_result = (validate-path-values $config)
|
|
let types_result = (validate-data-types $config)
|
|
let semantic_result = (validate-semantic-rules $config)
|
|
let files_result = (validate-file-existence $config)
|
|
|
|
# Combine all results
|
|
let all_errors = (
|
|
$structure_result.errors | append $paths_result.errors | append $types_result.errors |
|
|
append $semantic_result.errors | append $files_result.errors
|
|
)
|
|
|
|
let all_warnings = (
|
|
$structure_result.warnings | append $paths_result.warnings | append $types_result.warnings |
|
|
append $semantic_result.warnings | append $files_result.warnings
|
|
)
|
|
|
|
let has_errors = ($all_errors | length) > 0
|
|
let has_warnings = ($all_warnings | length) > 0
|
|
|
|
# In strict mode, treat warnings as errors
|
|
let final_valid = if $strict {
|
|
not $has_errors and not $has_warnings
|
|
} else {
|
|
not $has_errors
|
|
}
|
|
|
|
# Throw error if validation fails and not in detailed mode
|
|
if not $detailed and not $final_valid {
|
|
let error_messages = ($all_errors | each { |err| $err.message })
|
|
let warning_messages = if $strict { ($all_warnings | each { |warn| $warn.message }) } else { [] }
|
|
let combined_messages = ($error_messages | append $warning_messages)
|
|
|
|
error make {
|
|
msg: ($combined_messages | str join "; ")
|
|
}
|
|
}
|
|
|
|
# Return detailed results
|
|
{
|
|
valid: $final_valid,
|
|
errors: $all_errors,
|
|
warnings: $all_warnings,
|
|
summary: {
|
|
total_errors: ($all_errors | length),
|
|
total_warnings: ($all_warnings | length),
|
|
checks_run: 5,
|
|
structure_valid: $structure_result.valid,
|
|
paths_valid: $paths_result.valid,
|
|
types_valid: $types_result.valid,
|
|
semantic_valid: $semantic_result.valid,
|
|
files_valid: $files_result.valid
|
|
}
|
|
}
|
|
}
|