feat: add config module and agent setup for migration

- Add config module from backup (loader, accessor, migration)
- Add agent reference cards for token-efficient migration
- Add migration knowledge base and instructions
- Ready to start systematic config-driven migration
This commit is contained in:
Jesús Pérez 2025-09-22 23:36:59 +01:00
parent a509868257
commit 0a837aed54
7 changed files with 1402 additions and 0 deletions

View File

@ -0,0 +1,37 @@
# Agent Instructions Template (Token-Efficient)
## Syntax Fixer Agent
```markdown
TASK: Fix syntax errors in FILE_PATH
PATTERNS: See SYNTAX_FIX_CARD.md
OUTPUT: {"status": "fixed", "changes": N, "test_passed": bool}
TEST: nu --ide-check FILE_PATH
```
## ENV Migrator Agent
```markdown
TASK: Replace ENV vars in FILE_PATH
MAPPING: See ENV_MAPPING_CARD.md
KEEP: PROVISIONING_ARGS, PROVISIONING_OUT, NOW, CURRENT_*
OUTPUT: {"status": "migrated", "replacements": N}
```
## Validator Agent
```markdown
TASK: Test FILE_PATH works
COMMANDS: nu --ide-check FILE_PATH
OUTPUT: {"syntax_valid": bool, "errors": []}
```
## File Analyzer Agent
```markdown
TASK: Find issues in FILE_PATH
FIND: syntax errors, env refs, hardcoded paths
OUTPUT: {"syntax_errors": [], "env_refs": [], "paths": []}
```
## Token Budget
- Single file: ~500-1000 tokens
- Pattern reference: ~200 tokens
- Instructions: ~300 tokens
- Total per agent: ~1000-1500 tokens

View File

@ -0,0 +1,35 @@
# ENV → Accessor Mapping (Token-Efficient Reference)
## Critical Mappings
### Core Variables
```nushell
$env.PROVISIONING → (get-base-path)
$env.PROVISIONING_DEBUG → (is-debug-enabled)
$env.PROVISIONING_OUT → (get-provisioning-out)
$env.PROVISIONING_ARGS → (get-provisioning-args)
$env.PROVISIONING_MODULE → (get-provisioning-module)
$env.PROVISIONING_NAME → (get-provisioning-name)
```
### Path Variables
```nushell
$env.PROVISIONING_PROVIDERS_PATH → (get-providers-path)
$env.PROVISIONING_TASKSERVS_PATH → (get-taskservs-path)
$env.PROVISIONING_TOOLS_PATH → (get-tools-path)
$env.PROVISIONING_TEMPLATES_PATH → (get-templates-path)
```
### Runtime Variables (Keep as ENV)
```nushell
$env.PROVISIONING_ARGS # Command arguments - KEEP
$env.PROVISIONING_OUT # Output redirection - KEEP
$env.NOW # Timestamps - KEEP
$env.CURRENT_* # Context variables - KEEP
```
## Usage Pattern
```nushell
# Always wrap in parentheses
let value = ((get-function-name) | default "fallback")
```

View File

@ -0,0 +1,38 @@
# Syntax Fix Patterns (Token-Efficient Reference)
## Critical Patterns to Fix
### 1. Function Call Parentheses
```nushell
# BROKEN
$"(get-provisioning-args)? | default "") "
# FIXED
((get-provisioning-args) | default "")
```
### 2. Command Parentheses
```nushell
# BROKEN
^$"(get-provisioning-name))" -mod server
# FIXED
^(get-provisioning-name) -mod server
```
### 3. Missing Function Calls
```nushell
# BROKEN
let ops = $"(get-provisioning-args)? | default "") "
# FIXED
let ops = ((get-provisioning-args) | default "")
```
## Testing Command
```bash
nu --ide-check FILE_PATH
```
## Success Pattern
All function calls must be wrapped in parentheses when used in expressions.

View File

@ -0,0 +1,715 @@
# Configuration Accessor - Provides easy access to configuration values
# This module provides helper functions to access configuration safely
use std log
# Configuration cache (note: Nushell doesn't have persistent global state)
# This is a placeholder for documentation purposes
# Get the global configuration (loads and caches on first access)
export def get-config [
--reload = false # Force reload configuration
--debug = false # Enable debug logging
--environment: string # Override environment
] {
# Always reload since Nushell doesn't have persistent global state
use loader.nu load-provisioning-config
load-provisioning-config --debug=$debug --environment=$environment
}
# Get a configuration value using dot notation (e.g., "paths.base")
export def config-get [
path: string # Configuration path (e.g., "paths.base")
default_value: any = null # Default value if path not found
--config: record # Optional pre-loaded config
] {
let config_data = if ($config | is-empty) {
get-config
} else {
$config
}
use loader.nu get-config-value
get-config-value $config_data $path $default_value
}
# Check if a configuration path exists
export def config-has [
path: string # Configuration path to check
--config: record # Optional pre-loaded config
] {
let config_data = if ($config | is-empty) {
get-config
} else {
$config
}
let value = (config-get $path null --config $config_data)
($value | is-not-empty)
}
# Get all paths configuration as a convenient record
export def get-paths [
--config: record # Optional pre-loaded config
] {
config-get "paths" {} --config $config
}
# Get debug configuration
export def get-debug [
--config: record # Optional pre-loaded config
] {
config-get "debug" {} --config $config
}
# Get SOPS configuration
export def get-sops [
--config: record # Optional pre-loaded config
] {
config-get "sops" {} --config $config
}
# Get validation configuration
export def get-validation [
--config: record # Optional pre-loaded config
] {
config-get "validation" {} --config $config
}
# Get output configuration
export def get-output [
--config: record # Optional pre-loaded config
] {
config-get "output" {} --config $config
}
# Check if debug is enabled
export def is-debug-enabled [
--config: record # Optional pre-loaded config
] {
config-get "debug.enabled" false --config $config
}
# Get the base provisioning path
export def get-base-path [
--config: record # Optional pre-loaded config
] {
config-get "paths.base" "/usr/local/provisioning" --config $config
}
# Get the kloud path
export def get-kloud-path [
--config: record # Optional pre-loaded config
] {
config-get "paths.kloud" "" --config $config
}
# Get SOPS key search paths
export def get-sops-key-paths [
--config: record # Optional pre-loaded config
] {
config-get "sops.key_search_paths" [] --config $config
}
# Find the first existing SOPS key file
export def find-sops-key [
--config: record # Optional pre-loaded config
] {
let key_paths = (get-sops-key-paths --config $config)
for path in $key_paths {
if ($path | path exists) {
return $path
}
}
""
}
# Set up environment variables for backward compatibility
export def setup-env-compat [
--config: record # Optional pre-loaded config
] {
let config_data = if ($config | is-empty) {
get-config
} else {
$config
}
# Set up key environment variables for backward compatibility
$env.PROVISIONING = (config-get "paths.base" "/usr/local/provisioning" --config $config_data)
$env.PROVISIONING_KLOUD_PATH = (config-get "paths.kloud" "" --config $config_data)
$env.PROVISIONING_DEBUG = (config-get "debug.enabled" false --config $config_data | into string)
$env.PROVISIONING_USE_SOPS = (config-get "sops.use_sops" "age" --config $config_data)
# Set SOPS key if found
let sops_key = (find-sops-key --config $config_data)
if ($sops_key | is-not-empty) {
$env.SOPS_AGE_KEY_FILE = $sops_key
}
}
# Show current configuration (useful for debugging)
export def show-config [
--section: string # Show only a specific section
--format: string = "yaml" # Output format (yaml, json, table)
] {
let config_data = (get-config)
let output_data = if ($section | is-not-empty) {
config-get $section {} --config $config_data
} else {
$config_data
}
match $format {
"json" => { $output_data | to json --indent 2 | print }
"table" => { $output_data | print }
_ => { $output_data | to yaml | print }
}
}
# Validate current configuration and show any issues
export def validate-current-config [] {
let config_data = (get-config --debug=true)
print "✅ Configuration is valid"
}
# Helper functions to replace common (get-provisioning-* patterns
# Get provisioning name
export def get-provisioning-name [
--config: record # Optional pre-loaded config
] {
config-get "core.name" "provisioning" --config $config
}
# Get provisioning args
export def get-provisioning-args [
--config: record # Optional pre-loaded config
] {
$env.PROVISIONING_ARGS? | default ""
}
# Get provisioning output path
export def get-provisioning-out [
--config: record # Optional pre-loaded config
] {
$env.PROVISIONING_OUT? | default ""
}
# Check if no-terminal mode is enabled
export def is-no-terminal [
--config: record # Optional pre-loaded config
] {
config-get "debug.no_terminal" false --config $config
}
# Get work format (yaml/json)
export def get-work-format [
--config: record # Optional pre-loaded config
] {
config-get "output.format" "yaml" --config $config
}
# Get providers path
export def get-providers-path [
--config: record # Optional pre-loaded config
] {
config-get "paths.providers" "" --config $config
}
# Get taskservs path
export def get-taskservs-path [
--config: record # Optional pre-loaded config
] {
config-get "paths.taskservs" "" --config $config
}
# Get current timestamp
export def get-now [] {
$env.NOW? | default (date now | format date "%Y_%m_%d_%H_%M_%S")
}
# Check if metadata is enabled
export def is-metadata-enabled [
--config: record # Optional pre-loaded config
] {
config-get "debug.metadata" false --config $config
}
# Check if debug check is enabled
export def is-debug-check-enabled [
--config: record # Optional pre-loaded config
] {
config-get "debug.check" false --config $config
}
# Helper functions for non-PROVISIONING environment variables
# Get SSH options
export def get-ssh-options [
--config: record # Optional pre-loaded config
] {
config-get "ssh.options" [] --config $config
}
# Get current infrastructure path
export def get-current-infra-path [] {
$env.CURRENT_INFRA_PATH? | default ($env.PWD? | default "")
}
# Get current kloud path
export def get-current-kloud-path [] {
$env.CURRENT_KLOUD_PATH? | default ""
}
# Get SOPS age key file path
export def get-sops-age-key-file [
--config: record # Optional pre-loaded config
] {
let sops_key = (find-sops-key --config $config)
if ($sops_key | is-not-empty) { $sops_key } else { "" }
}
# Get SOPS age recipients
export def get-sops-age-recipients [
--config: record # Optional pre-loaded config
] {
$env.SOPS_AGE_RECIPIENTS? | default ""
}
# Get KCL module path
export def get-kcl-mod-path [
--config: record # Optional pre-loaded config
] {
let config_data = if ($config | is-empty) { get-config } else { $config }
let base_path = (config-get "paths.base" "" --config $config_data)
let providers_path = (config-get "paths.providers" "" --config $config_data)
[
($base_path | path join "kcl")
$providers_path
($env.PWD? | default "")
] | uniq | str join ":"
}
# Get work variable for current context
export def get-wk-cnprov [] {
$env.WK_CNPROV? | default ""
}
# Setter functions for backward compatibility
# Set debug enabled state
export def set-debug-enabled [value: bool] {
$env.PROVISIONING_DEBUG = $value
}
# Set provisioning output path
export def set-provisioning-out [path: string] {
$env.PROVISIONING_OUT = $path
}
# Set no-terminal mode
export def set-provisioning-no-terminal [value: bool] {
$env.PROVISIONING_NO_TERMINAL = $value
}
# Set work context path
export def set-wk-cnprov [path: string] {
$env.WK_CNPROV = $path
}
# Set metadata enabled state
export def set-metadata-enabled [value: bool] {
$env.PROVISIONING_METADATA = $value
}
# Get provisioning work format
export def get-provisioning-wk-format [
--config: record # Optional pre-loaded config
] {
config-get "output.format" "yaml" --config $config
}
# Get provisioning version
export def get-provisioning-vers [
--config: record # Optional pre-loaded config
] {
config-get "core.version" "2.0.0" --config $config
}
# Get provisioning no terminal
export def get-provisioning-no-terminal [
--config: record # Optional pre-loaded config
] {
config-get "debug.no_terminal" false --config $config
}
# Get provisioning generate directory path
export def get-provisioning-generate-dirpath [
--config: record # Optional pre-loaded config
] {
config-get "paths.generate" "generate" --config $config
}
# Get provisioning generate defs file
export def get-provisioning-generate-defsfile [
--config: record # Optional pre-loaded config
] {
config-get "paths.files.defs" "defs.nu" --config $config
}
# Get provisioning required versions file path
export def get-provisioning-req-versions [
--config: record # Optional pre-loaded config
] {
config-get "paths.files.req_versions" "" --config $config
}
# Additional accessor functions for remaining variables
# Get provisioning vars path
export def get-provisioning-vars [
--config: record # Optional pre-loaded config
] {
config-get "paths.files.vars" "" --config $config
}
# Get provisioning work environment path
export def get-provisioning-wk-env-path [
--config: record # Optional pre-loaded config
] {
$env.PROVISIONING_WK_ENV_PATH? | default ""
}
# Get provisioning resources path
export def get-provisioning-resources [
--config: record # Optional pre-loaded config
] {
config-get "paths.resources" "" --config $config
}
# Get provisioning settings source path
export def get-provisioning-settings-src-path [
--config: record # Optional pre-loaded config
] {
$env.PROVISIONING_SETTINGS_SRC_PATH? | default ""
}
# Get provisioning infra path
export def get-provisioning-infra-path [
--config: record # Optional pre-loaded config
] {
$env.PROVISIONING_KLOUD_PATH? | default (config-get "paths.infra" "" --config $config)
}
# Get clusters path
export def get-clusters-path [
--config: record # Optional pre-loaded config
] {
config-get "paths.clusters" "" --config $config
}
# Get templates path
export def get-templates-path [
--config: record # Optional pre-loaded config
] {
config-get "paths.templates" "" --config $config
}
# Get tools path
export def get-tools-path [
--config: record # Optional pre-loaded config
] {
config-get "paths.tools" "" --config $config
}
# Get file viewer
export def get-file-viewer [
--config: record # Optional pre-loaded config
] {
config-get "output.file_viewer" "bat" --config $config
}
# Get notify icon path
export def get-notify-icon [
--config: record # Optional pre-loaded config
] {
config-get "paths.files.notify_icon" "" --config $config
}
# Get default settings file
export def get-default-settings [
--config: record # Optional pre-loaded config
] {
config-get "paths.files.settings" "settings.k" --config $config
}
# Get match date format
export def get-match-date [
--config: record # Optional pre-loaded config
] {
config-get "output.match_date" "%Y_%m_%d" --config $config
}
# Get provisioning module
export def get-provisioning-module [
--config: record # Optional pre-loaded config
] {
$env.PROVISIONING_MODULE? | default ""
}
# Set provisioning module
export def set-provisioning-module [value: string] {
$env.PROVISIONING_MODULE = $value
}
# Additional accessor functions for complete migration
# Get provisioning log level
export def get-provisioning-log-level [
--config: record
] {
config-get "debug.log_level" "" --config $config
}
# Check if debug remote is enabled
export def is-debug-remote-enabled [
--config: record
] {
config-get "debug.remote" false --config $config
}
# Get provisioning URL
export def get-provisioning-url [
--config: record
] {
config-get "core.url" "https://provisioning.systems" --config $config
}
# Get provisioning use SOPS
export def get-provisioning-use-sops [
--config: record
] {
config-get "sops.use_sops" "age" --config $config
}
# Get provisioning use KMS
export def get-provisioning-use-kms [
--config: record
] {
config-get "sops.use_kms" "" --config $config
}
# Get secret provider
export def get-secret-provider [
--config: record
] {
config-get "sops.secret_provider" "sops" --config $config
}
# Get AI enabled
export def get-ai-enabled [
--config: record
] {
config-get "ai.enabled" false --config $config
}
# Get AI provider
export def get-ai-provider [
--config: record
] {
config-get "ai.provider" "openai" --config $config
}
# Get last error
export def get-last-error [
--config: record
] {
$env.PROVISIONING_LAST_ERROR? | default ""
}
# Get run taskservs path
export def get-run-taskservs-path [
--config: record
] {
config-get "paths.run_taskservs" "taskservs" --config $config
}
# Get run clusters path
export def get-run-clusters-path [
--config: record
] {
config-get "paths.run_clusters" "clusters" --config $config
}
# Get keys path
export def get-keys-path [
--config: record
] {
config-get "paths.files.keys" ".keys.k" --config $config
}
# Get use KCL
export def get-use-kcl [
--config: record
] {
config-get "tools.use_kcl" false --config $config
}
# Get use KCL plugin
export def get-use-kcl-plugin [
--config: record
] {
config-get "tools.use_kcl_plugin" false --config $config
}
# Get use TERA plugin
export def get-use-tera-plugin [
--config: record
] {
config-get "tools.use_tera_plugin" false --config $config
}
# Get extensions path
export def get-extensions-path [
--config: record
] {
config-get "paths.extensions" "" --config $config
}
# Get extension mode
export def get-extension-mode [
--config: record
] {
config-get "extensions.mode" "full" --config $config
}
# Get provisioning profile
export def get-provisioning-profile [
--config: record
] {
config-get "extensions.profile" "" --config $config
}
# Get allowed extensions
export def get-allowed-extensions [
--config: record
] {
config-get "extensions.allowed" "" --config $config
}
# Get blocked extensions
export def get-blocked-extensions [
--config: record
] {
config-get "extensions.blocked" "" --config $config
}
# Get custom providers
export def get-custom-providers [
--config: record
] {
config-get "paths.custom_providers" "" --config $config
}
# Get custom taskservs
export def get-custom-taskservs [
--config: record
] {
config-get "paths.custom_taskservs" "" --config $config
}
# Get core nulib path
export def get-core-nulib-path [
--config: record
] {
let base = (get-base-path --config $config)
$base | path join "core" "nulib"
}
# Get prov lib path
export def get-prov-lib-path [
--config: record
] {
let providers = (get-providers-path --config $config)
$providers | path join "prov_lib"
}
# Get provisioning core path
export def get-provisioning-core [
--config: record
] {
let base = (get-base-path --config $config)
$base | path join "core"
}
# KMS (Key Management Service) accessor functions
export def get-kms-server [
--config: record
] {
config-get "kms.server" "" --config $config
}
export def get-kms-auth-method [
--config: record
] {
config-get "kms.auth_method" "certificate" --config $config
}
export def get-kms-client-cert [
--config: record
] {
config-get "kms.client_cert" "" --config $config
}
export def get-kms-client-key [
--config: record
] {
config-get "kms.client_key" "" --config $config
}
export def get-kms-ca-cert [
--config: record
] {
config-get "kms.ca_cert" "" --config $config
}
export def get-kms-api-token [
--config: record
] {
config-get "kms.api_token" "" --config $config
}
export def get-kms-username [
--config: record
] {
config-get "kms.username" "" --config $config
}
export def get-kms-password [
--config: record
] {
config-get "kms.password" "" --config $config
}
export def get-kms-timeout [
--config: record
] {
config-get "kms.timeout" "30" --config $config
}
export def get-kms-verify-ssl [
--config: record
] {
config-get "kms.verify_ssl" "true" --config $config
}
# Check if SSH debug mode is enabled
export def is-ssh-debug-enabled [
--config: record
] {
config-get "debug.ssh" false --config $config
}

View File

@ -0,0 +1,263 @@
# 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"
}
}
}

View File

@ -0,0 +1,262 @@
# Configuration Migration Tool
# Helps migrate from environment variable based configuration to the new config system
use std log
# Mapping of old environment variables to new config paths
export def get-env-mapping [] {
[
{ env_var: "PROVISIONING", config_path: "paths.base", description: "Base provisioning path" }
{ env_var: "PROVISIONING_KLOUD_PATH", config_path: "paths.kloud", description: "Infrastructure kloud path" }
{ env_var: "PROVISIONING_PROVIDERS_PATH", config_path: "paths.providers", description: "Providers path" }
{ env_var: "PROVISIONING_TASKSERVS_PATH", config_path: "paths.taskservs", description: "Task services path" }
{ env_var: "PROVISIONING_CLUSTERS_PATH", config_path: "paths.clusters", description: "Clusters path" }
{ env_var: "PROVISIONING_RESOURCES", config_path: "paths.resources", description: "Resources path" }
{ env_var: "PROVISIONING_TEMPLATES_PATH", config_path: "paths.templates", description: "Templates path" }
{ env_var: "PROVISIONING_TOOLS_PATH", config_path: "paths.tools", description: "Tools path" }
{ env_var: "PROVISIONING_CORE", config_path: "paths.core", description: "Core path" }
{ env_var: "PROVISIONING_DEBUG", config_path: "debug.enabled", description: "Debug mode" }
{ env_var: "PROVISIONING_METADATA", config_path: "debug.metadata", description: "Metadata debug" }
{ env_var: "PROVISIONING_DEBUG_CHECK", config_path: "debug.check", description: "Debug check mode" }
{ env_var: "PROVISIONING_DEBUG_REMOTE", config_path: "debug.remote", description: "Remote debug mode" }
{ env_var: "PROVISIONING_LOG_LEVEL", config_path: "debug.log_level", description: "Log level" }
{ env_var: "PROVISIONING_NO_TERMINAL", config_path: "debug.no_terminal", description: "No terminal mode" }
{ env_var: "PROVISIONING_FILEVIEWER", config_path: "output.file_viewer", description: "File viewer command" }
{ env_var: "PROVISIONING_WK_FORMAT", config_path: "output.format", description: "Working format" }
{ env_var: "PROVISIONING_USE_SOPS", config_path: "sops.use_sops", description: "SOPS encryption type" }
{ env_var: "PROVISIONING_KAGE", config_path: "sops.key_search_paths.0", description: "Primary SOPS key path" }
{ env_var: "PROVISIONING_SOPS", config_path: "sops.config_path", description: "SOPS config path" }
{ env_var: "PROVISIONING_DFLT_SET", config_path: "paths.files.settings", description: "Default settings file" }
{ env_var: "PROVISIONING_KEYS_PATH", config_path: "paths.files.keys", description: "Keys file path" }
{ env_var: "PROVISIONING_REQ_VERSIONS", config_path: "paths.files.requirements", description: "Requirements file" }
{ env_var: "PROVISIONING_NOTIFY_ICON", config_path: "paths.files.notify_icon", description: "Notification icon" }
{ env_var: "PROVISIONING_RUN_TASKSERVS_PATH", config_path: "taskservs.run_path", description: "Task services run path" }
{ env_var: "PROVISIONING_RUN_CLUSTERS_PATH", config_path: "clusters.run_path", description: "Clusters run path" }
{ env_var: "PROVISIONING_GENERATE_DIRPATH", config_path: "generation.dir_path", description: "Generation directory" }
{ env_var: "PROVISIONING_GENERATE_DEFSFILE", config_path: "generation.defs_file", description: "Generation definitions file" }
]
}
# Analyze current environment variables and suggest migration
export def analyze-current-env [] {
let mapping = (get-env-mapping)
mut analysis = []
for entry in $mapping {
let env_value = ($env | get -o $entry.env_var)
if ($env_value | is-not-empty) {
$analysis = ($analysis | append {
env_var: $entry.env_var
current_value: $env_value
config_path: $entry.config_path
description: $entry.description
action: "migrate"
})
} else {
$analysis = ($analysis | append {
env_var: $entry.env_var
current_value: "not set"
config_path: $entry.config_path
description: $entry.description
action: "default"
})
}
}
$analysis
}
# Generate user configuration based on current environment variables
export def generate-user-config [
--output: string = "" # Output file (default: ~/.config/provisioning/config.toml)
--dry-run = false # Show what would be generated without writing
] {
let analysis = (analyze-current-env)
let active_vars = ($analysis | where action == "migrate")
if ($active_vars | is-empty) {
print "No environment variables found to migrate"
return
}
# Build configuration structure
mut config = {
core: {
name: "provisioning"
}
paths: {}
debug: {}
output: {}
sops: {
key_search_paths: []
}
}
# Convert environment variables to config structure
for var in $active_vars {
$config = (set-config-value $config $var.config_path $var.current_value)
}
# Convert to TOML
let config_toml = ($config | to toml)
let output_path = if ($output | is-empty) {
($env.HOME | path join ".config" | path join "provisioning" | path join "config.toml")
} else {
$output
}
if $dry_run {
print "Generated configuration (dry run):"
print "=====================================\n"
print $config_toml
print $"\nWould be written to: ($output_path)"
} else {
# Ensure directory exists
let config_dir = ($output_path | path dirname)
if not ($config_dir | path exists) {
mkdir $config_dir
print $"Created directory: ($config_dir)"
}
# Write configuration
$config_toml | save $output_path
print $"Generated user configuration: ($output_path)"
print "Review and edit this file as needed"
}
}
# Set a nested configuration value using dot notation
def set-config-value [
config: record
path: string
value: any
] {
let path_parts = ($path | split row ".")
if ($path_parts | length) == 1 {
# Simple top-level assignment
return ($config | upsert ($path_parts | first) $value)
}
# Handle nested paths
let first_part = ($path_parts | first)
let remaining_path = ($path_parts | skip 1 | str join ".")
let existing_section = ($config | get -o $first_part | default {})
let updated_section = (set-config-value $existing_section $remaining_path $value)
$config | upsert $first_part $updated_section
}
# Check for potential issues in the migration
export def check-migration-issues [] {
let analysis = (analyze-current-env)
mut issues = []
# Check for conflicting paths
let base_path = ($env | get -o PROVISIONING)
if ($base_path | is-not-empty) and not ($base_path | path exists) {
$issues = ($issues | append {
type: "missing_path"
item: "PROVISIONING"
value: $base_path
issue: "Base path does not exist"
severity: "error"
})
}
# Check for SOPS configuration
let sops_key = ($env | get -o PROVISIONING_KAGE)
if ($sops_key | is-not-empty) and not ($sops_key | path exists) {
$issues = ($issues | append {
type: "missing_file"
item: "PROVISIONING_KAGE"
value: $sops_key
issue: "SOPS key file does not exist"
severity: "warning"
})
}
# Check for deprecated variables that should be removed
let deprecated_vars = [
"PROVISIONING_ARGS"
"PROVISIONING_MODULE"
"PROVISIONING_NAME"
"PROVISIONING_OUT"
"PROVISIONING_LAST_ERROR"
]
for var in $deprecated_vars {
let value = ($env | get -o $var)
if ($value | is-not-empty) {
$issues = ($issues | append {
type: "deprecated"
item: $var
value: $value
issue: "This variable is deprecated and should be removed"
severity: "info"
})
}
}
$issues
}
# Show migration status and recommendations
export def show-migration-status [] {
print "🔄 Environment Variable Migration Analysis"
print "==========================================\n"
let analysis = (analyze-current-env)
let to_migrate = ($analysis | where action == "migrate")
let using_defaults = ($analysis | where action == "default")
print $"📊 Summary:"
print $" • Variables to migrate: ($to_migrate | length)"
print $" • Using system defaults: ($using_defaults | length)"
print ""
if ($to_migrate | length) > 0 {
print "🔧 Variables that will be migrated:"
$to_migrate | select env_var current_value config_path | table --index false
print ""
}
let issues = (check-migration-issues)
if ($issues | length) > 0 {
print "⚠️ Potential issues found:"
$issues | table --index false
print ""
}
print "💡 Next steps:"
print " 1. Run 'migration generate-user-config --dry-run' to preview"
print " 2. Run 'migration generate-user-config' to create config file"
print " 3. Test the new configuration system"
print " 4. Remove old environment variables from your shell profile"
}
# Create a backup of current environment variables
export def backup-current-env [
output: string = "provisioning-env-backup.nu"
] {
let mapping = (get-env-mapping)
mut backup_content = "# Backup of provisioning environment variables\n"
$backup_content = ($backup_content + "# Generated on " + (date now | format date "%Y-%m-%d %H:%M:%S") + "\n\n")
for entry in $mapping {
let env_value = ($env | get -o $entry.env_var)
if ($env_value | is-not-empty) {
$backup_content = ($backup_content + $"$env.($entry.env_var) = \"($env_value)\"\n")
}
}
$backup_content | save $output
print $"Environment variables backed up to: ($output)"
}

View File

@ -0,0 +1,52 @@
# Configuration System Module Index
# Central import point for the new configuration system
# Core configuration functionality
export use loader.nu *
export use accessor.nu *
export use migration.nu *
# Convenience function to get the complete configuration
export def config [] {
get-config
}
# Quick access to common configuration sections
export def paths [] {
get-paths
}
export def debug [] {
get-debug
}
export def sops [] {
get-sops
}
export def validation [] {
get-validation
}
# Migration helpers
export def migrate [] {
use migration.nu show-migration-status
show-migration-status
}
export def migrate-now [
--dry-run = false
] {
use migration.nu generate-user-config
generate-user-config --dry-run $dry_run
}
# Configuration validation
export def validate [] {
validate-current-config
}
# Initialize user configuration
export def init [] {
init-user-config
}