# Module: Configuration Interpolators # Purpose: Handles variable substitution and interpolation in configuration values using templates and expressions. # Dependencies: None (core utility) # Interpolation Engine - Handles variable substitution in configuration # Supports: environment variables, datetime, git info, SOPS config, provider references, advanced features # Primary entry point: Interpolate all paths in configuration export def interpolate-config [ config: record ] { mut result = $config # Get base path for interpolation let base_path = ($config | get -o paths.base | default "") if ($base_path | is-not-empty) { # Interpolate the entire config structure $result = (interpolate-all-paths $result $base_path) } $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-internal $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 } # Helper function to get nested configuration value using dot notation def get-config-value-internal [ config: record path: string default_value: any = null ] { let path_parts = ($path | split row ".") mut current = $config for part in $path_parts { let immutable_current = $current let next_value = ($immutable_current | get -o $part | default null) if ($next_value | is-empty) { return $default_value } $current = $next_value } $current } # Enhanced interpolation function with comprehensive pattern support def interpolate-all-paths [ config: record base_path: string ] { # Convert to JSON for efficient string processing let json_str = ($config | to json) # Start with existing pattern mut interpolated_json = ($json_str | str replace --all "{{paths.base}}" $base_path) # Apply enhanced interpolation patterns $interpolated_json = (apply-enhanced-interpolation $interpolated_json $config) # Convert back to record ($interpolated_json | from json) } # Apply enhanced interpolation patterns with security validation def apply-enhanced-interpolation [ json_str: string config: record ] { mut result = $json_str # Environment variable interpolation with security checks $result = (interpolate-env-variables $result) # Date and time interpolation $result = (interpolate-datetime $result) # Git information interpolation $result = (interpolate-git-info $result) # SOPS configuration interpolation $result = (interpolate-sops-config $result $config) # Cross-section provider references $result = (interpolate-provider-refs $result $config) # Advanced features: conditionals and functions $result = (interpolate-advanced-features $result $config) $result } # Interpolate environment variables with security validation def interpolate-env-variables [ text: string ] { mut result = $text # Safe environment variables list (security) let safe_env_vars = [ "HOME" "USER" "HOSTNAME" "PWD" "SHELL" "PROVISIONING" "PROVISIONING_WORKSPACE_PATH" "PROVISIONING_INFRA_PATH" "PROVISIONING_SOPS" "PROVISIONING_KAGE" ] for env_var in $safe_env_vars { let pattern = $"\\{\\{env\\.($env_var)\\}\\}" let env_value = ($env | get -o $env_var | default "") if ($env_value | is-not-empty) { $result = ($result | str replace --regex $pattern $env_value) } } # Handle conditional environment variables like {{env.HOME || "/tmp"}} $result = (interpolate-conditional-env $result) $result } # Handle conditional environment variable interpolation def interpolate-conditional-env [ text: string ] { mut result = $text # For now, implement basic conditional logic for common patterns if ($result | str contains "{{env.HOME || \"/tmp\"}}") { let home_value = ($env.HOME? | default "/tmp") $result = ($result | str replace --all "{{env.HOME || \"/tmp\"}}" $home_value) } if ($result | str contains "{{env.USER || \"unknown\"}}") { let user_value = ($env.USER? | default "unknown") $result = ($result | str replace --all "{{env.USER || \"unknown\"}}" $user_value) } $result } # Interpolate date and time values def interpolate-datetime [ text: string ] { mut result = $text # Current date in YYYY-MM-DD format let current_date = (date now | format date "%Y-%m-%d") $result = ($result | str replace --all "{{now.date}}" $current_date) # Current timestamp (Unix timestamp) let current_timestamp = (date now | format date "%s") $result = ($result | str replace --all "{{now.timestamp}}" $current_timestamp) # ISO 8601 timestamp let iso_timestamp = (date now | format date "%Y-%m-%dT%H:%M:%SZ") $result = ($result | str replace --all "{{now.iso}}" $iso_timestamp) $result } # Interpolate git information def interpolate-git-info [ text: string ] { mut result = $text # Get git branch (skip to avoid hanging) let git_branch = "unknown" $result = ($result | str replace --all "{{git.branch}}" $git_branch) # Get git commit hash (skip to avoid hanging) let git_commit = "unknown" $result = ($result | str replace --all "{{git.commit}}" $git_commit) # Get git remote origin URL (skip to avoid hanging) # Note: Skipped due to potential hanging on network/credential prompts let git_origin = "unknown" $result = ($result | str replace --all "{{git.origin}}" $git_origin) $result } # Interpolate SOPS configuration references def interpolate-sops-config [ text: string config: record ] { mut result = $text # SOPS key file path let sops_key_file = ($config | get -o sops.age_key_file | default "") if ($sops_key_file | is-not-empty) { $result = ($result | str replace --all "{{sops.key_file}}" $sops_key_file) } # SOPS config path let sops_config_path = ($config | get -o sops.config_path | default "") if ($sops_config_path | is-not-empty) { $result = ($result | str replace --all "{{sops.config_path}}" $sops_config_path) } $result } # Interpolate cross-section provider references def interpolate-provider-refs [ text: string config: record ] { mut result = $text # AWS provider region let aws_region = ($config | get -o providers.aws.region | default "") if ($aws_region | is-not-empty) { $result = ($result | str replace --all "{{providers.aws.region}}" $aws_region) } # Default provider let default_provider = ($config | get -o providers.default | default "") if ($default_provider | is-not-empty) { $result = ($result | str replace --all "{{providers.default}}" $default_provider) } # UpCloud zone let upcloud_zone = ($config | get -o providers.upcloud.zone | default "") if ($upcloud_zone | is-not-empty) { $result = ($result | str replace --all "{{providers.upcloud.zone}}" $upcloud_zone) } $result } # Interpolate advanced features (function calls, environment-aware paths) def interpolate-advanced-features [ text: string config: record ] { mut result = $text # Function call: {{path.join(paths.base, "custom")}} if ($result | str contains "{{path.join(paths.base") { let base_path = ($config | get -o paths.base | default "") # Simple implementation for path.join with base path $result = ($result | str replace --regex "\\{\\{path\\.join\\(paths\\.base,\\s*\"([^\"]+)\"\\)\\}\\}" $"($base_path)/$1") } # Environment-aware paths: {{paths.base.${env}}} let current_env = ($config | get -o current_environment | default "dev") $result = ($result | str replace --all "{{paths.base.${env}}}" $"{{paths.base}}.($current_env)") $result } # Interpolate with depth limiting to prevent infinite recursion export def interpolate-with-depth-limit [ config: record base_path: string max_depth: int ] { mut result = $config mut current_depth = 0 # Track interpolation patterns to detect loops mut seen_patterns = [] while $current_depth < $max_depth { let pre_interpolation = ($result | to json) $result = (interpolate-all-paths $result $base_path) let post_interpolation = ($result | to json) # If no changes, we're done if $pre_interpolation == $post_interpolation { break } # Check for circular dependencies if ($post_interpolation in $seen_patterns) { error make { msg: $"Circular interpolation dependency detected at depth ($current_depth)" } } $seen_patterns = ($seen_patterns | append $post_interpolation) $current_depth = ($current_depth + 1) } if $current_depth >= $max_depth { error make { msg: $"Maximum interpolation depth ($max_depth) exceeded - possible infinite recursion" } } $result }