263 lines
7.6 KiB
Plaintext
263 lines
7.6 KiB
Plaintext
![]() |
# Configuration Loader for Provisioning System
|
||
|
# Implements hierarchical configuration loading with variable interpolation
|
||
|
|
||
|
use std log
|
||
|
|
||
|
# Main configuration loader - loads and merges all config sources
|
||
|
export def load-provisioning-config [
|
||
|
--debug = false # Enable debug logging
|
||
|
--validate = true # Validate configuration
|
||
|
--environment: string # Override environment (dev/prod/test)
|
||
|
] {
|
||
|
if $debug {
|
||
|
# log debug "Loading provisioning configuration..."
|
||
|
}
|
||
|
|
||
|
# Define configuration sources in precedence order (lowest to highest)
|
||
|
let config_sources = [
|
||
|
# 1. System defaults (lowest precedence)
|
||
|
{
|
||
|
name: "defaults"
|
||
|
path: "/Users/Akasha/repo-cnz/src/provisioning/config.defaults.toml"
|
||
|
required: true
|
||
|
}
|
||
|
# 2. User configuration
|
||
|
{
|
||
|
name: "user"
|
||
|
path: ($env.HOME | path join ".config" | path join "provisioning" | path join "config.toml")
|
||
|
required: false
|
||
|
}
|
||
|
# 3. Project configuration
|
||
|
{
|
||
|
name: "project"
|
||
|
path: ($env.PWD | path join "provisioning.toml")
|
||
|
required: false
|
||
|
}
|
||
|
# 4. Infrastructure-specific configuration (highest precedence)
|
||
|
{
|
||
|
name: "infra"
|
||
|
path: ($env.PWD | path join ".provisioning.toml")
|
||
|
required: false
|
||
|
}
|
||
|
]
|
||
|
|
||
|
mut final_config = {}
|
||
|
|
||
|
# Load and merge configurations
|
||
|
for source in $config_sources {
|
||
|
let config_data = (load-config-file $source.path $source.required $debug)
|
||
|
if ($config_data | is-not-empty) {
|
||
|
if $debug {
|
||
|
# log debug $"Loaded ($source.name) config from ($source.path)"
|
||
|
}
|
||
|
$final_config = (deep-merge $final_config $config_data)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Apply environment-specific overrides if specified
|
||
|
if ($environment | is-not-empty) {
|
||
|
let env_config = ($final_config | get -o $"environments.($environment)" | default {})
|
||
|
if ($env_config | is-not-empty) {
|
||
|
if $debug {
|
||
|
# log debug $"Applying environment overrides for: ($environment)"
|
||
|
}
|
||
|
$final_config = (deep-merge $final_config $env_config)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Interpolate variables in the final configuration
|
||
|
$final_config = (interpolate-config $final_config)
|
||
|
|
||
|
# Validate configuration if requested
|
||
|
if $validate {
|
||
|
validate-config $final_config
|
||
|
}
|
||
|
|
||
|
if $debug {
|
||
|
# log debug "Configuration loading completed"
|
||
|
}
|
||
|
|
||
|
$final_config
|
||
|
}
|
||
|
|
||
|
# Load a single configuration file
|
||
|
export def load-config-file [
|
||
|
file_path: string
|
||
|
required = false
|
||
|
debug = false
|
||
|
] {
|
||
|
if not ($file_path | path exists) {
|
||
|
if $required {
|
||
|
error make {
|
||
|
msg: $"Required configuration file not found: ($file_path)"
|
||
|
}
|
||
|
} else {
|
||
|
if $debug {
|
||
|
# log debug $"Optional config file not found: ($file_path)"
|
||
|
}
|
||
|
return {}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if $debug {
|
||
|
# log debug $"Loading config file: ($file_path)"
|
||
|
}
|
||
|
|
||
|
# Load the file - use error handling without complex try-catch
|
||
|
if ($file_path | path exists) {
|
||
|
open $file_path
|
||
|
} else {
|
||
|
if $required {
|
||
|
error make {
|
||
|
msg: $"Configuration file not found: ($file_path)"
|
||
|
}
|
||
|
} else {
|
||
|
{}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Deep merge two configuration records (right takes precedence)
|
||
|
export def deep-merge [
|
||
|
base: record
|
||
|
override: record
|
||
|
] {
|
||
|
mut result = $base
|
||
|
|
||
|
for key in ($override | columns) {
|
||
|
let override_value = ($override | get $key)
|
||
|
let base_value = ($base | get -o $key)
|
||
|
|
||
|
if ($base_value | is-empty) {
|
||
|
# Key doesn't exist in base, add it
|
||
|
$result = ($result | insert $key $override_value)
|
||
|
} else if (($base_value | describe) == "record") and (($override_value | describe) == "record") {
|
||
|
# Both are records, merge recursively
|
||
|
$result = ($result | upsert $key (deep-merge $base_value $override_value))
|
||
|
} else {
|
||
|
# Override the value
|
||
|
$result = ($result | upsert $key $override_value)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$result
|
||
|
}
|
||
|
|
||
|
# Interpolate variables in configuration values
|
||
|
export def interpolate-config [
|
||
|
config: record
|
||
|
] {
|
||
|
mut result = $config
|
||
|
|
||
|
# First pass: resolve simple base values
|
||
|
if ($config | get -o paths.base | is-not-empty) {
|
||
|
let base_path = ($config | get paths.base)
|
||
|
$result = ($result | update paths (
|
||
|
$result | get paths |
|
||
|
transpose key value |
|
||
|
each {|item|
|
||
|
if ($item.value | describe) == "string" and ($item.value | str contains "${paths.base}") {
|
||
|
{key: $item.key, value: ($item.value | str replace --all "${paths.base}" $base_path)}
|
||
|
} else {
|
||
|
$item
|
||
|
}
|
||
|
} |
|
||
|
transpose -r |
|
||
|
into record
|
||
|
))
|
||
|
}
|
||
|
|
||
|
$result
|
||
|
}
|
||
|
|
||
|
# Interpolate variables in a string using ${path.to.value} syntax
|
||
|
export def interpolate-string [
|
||
|
text: string
|
||
|
config: record
|
||
|
] {
|
||
|
mut result = $text
|
||
|
|
||
|
# Simple interpolation for ${paths.base} pattern
|
||
|
if ($result | str contains "${paths.base}") {
|
||
|
let base_path = (get-config-value $config "paths.base" "")
|
||
|
$result = ($result | str replace --all "${paths.base}" $base_path)
|
||
|
}
|
||
|
|
||
|
# Add more interpolation patterns as needed
|
||
|
# This is a basic implementation - a full template engine would be more robust
|
||
|
$result
|
||
|
}
|
||
|
|
||
|
# Get a nested configuration value using dot notation
|
||
|
export def get-config-value [
|
||
|
config: record
|
||
|
path: string
|
||
|
default_value: any = null
|
||
|
] {
|
||
|
let path_parts = ($path | split row ".")
|
||
|
mut current = $config
|
||
|
|
||
|
for part in $path_parts {
|
||
|
let next_value = ($current | get -o $part)
|
||
|
if ($next_value | is-empty) {
|
||
|
return $default_value
|
||
|
}
|
||
|
$current = $next_value
|
||
|
}
|
||
|
|
||
|
$current
|
||
|
}
|
||
|
|
||
|
# Validate the final configuration
|
||
|
export def validate-config [
|
||
|
config: record
|
||
|
] {
|
||
|
# Check required top-level sections
|
||
|
let required_sections = ["core", "paths", "debug"]
|
||
|
|
||
|
for section in $required_sections {
|
||
|
if ($config | get -o $section | is-empty) {
|
||
|
error make {
|
||
|
msg: $"Missing required configuration section: ($section)"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Validate core section
|
||
|
let core = ($config | get core)
|
||
|
if ($core | get -o version | is-empty) {
|
||
|
error make {
|
||
|
msg: "Missing required core.version in configuration"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Validate paths section
|
||
|
let paths = ($config | get paths)
|
||
|
if ($paths | get -o base | is-empty) {
|
||
|
error make {
|
||
|
msg: "Missing required paths.base in configuration"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Additional validation can be added here
|
||
|
}
|
||
|
|
||
|
# Helper function to create directory structure for user config
|
||
|
export def init-user-config [] {
|
||
|
let config_dir = ($env.HOME | path join ".config" | path join "provisioning")
|
||
|
|
||
|
if not ($config_dir | path exists) {
|
||
|
mkdir $config_dir
|
||
|
print $"Created user config directory: ($config_dir)"
|
||
|
}
|
||
|
|
||
|
let user_config_path = ($config_dir | path join "config.toml")
|
||
|
if not ($user_config_path | path exists) {
|
||
|
let example_path = ($env.FILE_PWD | path join ".." | path join ".." | path join ".." | path join ".." | path join "config.user.toml.example")
|
||
|
if ($example_path | path exists) {
|
||
|
cp $example_path $user_config_path
|
||
|
print $"Created user config file: ($user_config_path)"
|
||
|
print "Edit this file to customize your provisioning settings"
|
||
|
}
|
||
|
}
|
||
|
}
|