312 lines
9.2 KiB
Text
312 lines
9.2 KiB
Text
|
|
# 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
|
||
|
|
}
|