provisioning-core/nulib/domain/config/interpolators.nu

311 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
}