diff --git a/.migration/knowledge/AGENT_INSTRUCTIONS.md b/.migration/knowledge/AGENT_INSTRUCTIONS.md new file mode 100644 index 0000000..9c05529 --- /dev/null +++ b/.migration/knowledge/AGENT_INSTRUCTIONS.md @@ -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 \ No newline at end of file diff --git a/.migration/knowledge/ENV_MAPPING_CARD.md b/.migration/knowledge/ENV_MAPPING_CARD.md new file mode 100644 index 0000000..cbbad41 --- /dev/null +++ b/.migration/knowledge/ENV_MAPPING_CARD.md @@ -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") +``` \ No newline at end of file diff --git a/.migration/knowledge/SYNTAX_FIX_CARD.md b/.migration/knowledge/SYNTAX_FIX_CARD.md new file mode 100644 index 0000000..f8bdc1f --- /dev/null +++ b/.migration/knowledge/SYNTAX_FIX_CARD.md @@ -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. \ No newline at end of file diff --git a/core/nulib/lib_provisioning/config/accessor.nu b/core/nulib/lib_provisioning/config/accessor.nu new file mode 100644 index 0000000..001d738 --- /dev/null +++ b/core/nulib/lib_provisioning/config/accessor.nu @@ -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 +} \ No newline at end of file diff --git a/core/nulib/lib_provisioning/config/loader.nu b/core/nulib/lib_provisioning/config/loader.nu new file mode 100644 index 0000000..2aa20e3 --- /dev/null +++ b/core/nulib/lib_provisioning/config/loader.nu @@ -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" + } + } +} \ No newline at end of file diff --git a/core/nulib/lib_provisioning/config/migration.nu b/core/nulib/lib_provisioning/config/migration.nu new file mode 100644 index 0000000..318721c --- /dev/null +++ b/core/nulib/lib_provisioning/config/migration.nu @@ -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)" +} \ No newline at end of file diff --git a/core/nulib/lib_provisioning/config/mod.nu b/core/nulib/lib_provisioning/config/mod.nu new file mode 100644 index 0000000..7895fbf --- /dev/null +++ b/core/nulib/lib_provisioning/config/mod.nu @@ -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 +} \ No newline at end of file