173 lines
7.1 KiB
Text
173 lines
7.1 KiB
Text
|
|
# Environment detection and management helper functions
|
||
|
|
# NUSHELL 0.109 COMPLIANT - Using do-complete (Rule 5), each (Rule 8)
|
||
|
|
|
||
|
|
# Detect current environment from system context
|
||
|
|
# Priority: PROVISIONING_ENV > CI/CD > git/dev markers > HOSTNAME > NODE_ENV > TERM > default
|
||
|
|
export def detect-current-environment [] {
|
||
|
|
# Check explicit environment variable
|
||
|
|
if ($env.PROVISIONING_ENV? | is-not-empty) {
|
||
|
|
return $env.PROVISIONING_ENV
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check CI/CD environments
|
||
|
|
if ($env.CI? | is-not-empty) {
|
||
|
|
if ($env.GITHUB_ACTIONS? | is-not-empty) { return "ci" }
|
||
|
|
if ($env.GITLAB_CI? | is-not-empty) { return "ci" }
|
||
|
|
if ($env.JENKINS_URL? | is-not-empty) { return "ci" }
|
||
|
|
return "test"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check for development indicators
|
||
|
|
if (($env.PWD | path join ".git" | path exists) or
|
||
|
|
($env.PWD | path join "development" | path exists) or
|
||
|
|
($env.PWD | path join "dev" | path exists)) {
|
||
|
|
return "dev"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check for production indicators
|
||
|
|
if (($env.HOSTNAME? | default "" | str contains "prod") or
|
||
|
|
($env.NODE_ENV? | default "" | str downcase) == "production" or
|
||
|
|
($env.ENVIRONMENT? | default "" | str downcase) == "production") {
|
||
|
|
return "prod"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check for test indicators
|
||
|
|
if (($env.NODE_ENV? | default "" | str downcase) == "test" or
|
||
|
|
($env.ENVIRONMENT? | default "" | str downcase) == "test") {
|
||
|
|
return "test"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Default to development for interactive usage
|
||
|
|
if ($env.TERM? | is-not-empty) {
|
||
|
|
return "dev"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Fallback
|
||
|
|
"dev"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get available environments from configuration
|
||
|
|
export def get-available-environments [config: record] {
|
||
|
|
let env_section_result = (do { $config | get "environments" } | complete)
|
||
|
|
let environments_section = if $env_section_result.exit_code == 0 { $env_section_result.stdout } else { {} }
|
||
|
|
$environments_section | columns
|
||
|
|
}
|
||
|
|
|
||
|
|
# Validate environment name
|
||
|
|
export def validate-environment [environment: string, config: record] {
|
||
|
|
let valid_environments = ["dev" "test" "prod" "ci" "staging" "local"]
|
||
|
|
let configured_environments = (get-available-environments $config)
|
||
|
|
let all_valid = ($valid_environments | append $configured_environments | uniq)
|
||
|
|
|
||
|
|
if ($environment in $all_valid) {
|
||
|
|
{ valid: true, message: "" }
|
||
|
|
} else {
|
||
|
|
{
|
||
|
|
valid: false,
|
||
|
|
message: $"Invalid environment '($environment)'. Valid options: ($all_valid | str join ', ')"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Set a configuration value using dot notation path (e.g., "debug.log_level")
|
||
|
|
def set-config-value [config: record, path: string, value: any] {
|
||
|
|
let path_parts = ($path | split row ".")
|
||
|
|
|
||
|
|
match ($path_parts | length) {
|
||
|
|
1 => {
|
||
|
|
$config | upsert ($path_parts | first) $value
|
||
|
|
}
|
||
|
|
2 => {
|
||
|
|
let section = ($path_parts | first)
|
||
|
|
let key = ($path_parts | last)
|
||
|
|
let section_result = (do { $config | get $section } | complete)
|
||
|
|
let section_data = if $section_result.exit_code == 0 { $section_result.stdout } else { {} }
|
||
|
|
$config | upsert $section ($section_data | upsert $key $value)
|
||
|
|
}
|
||
|
|
3 => {
|
||
|
|
let section = ($path_parts | first)
|
||
|
|
let subsection = ($path_parts | get 1)
|
||
|
|
let key = ($path_parts | last)
|
||
|
|
let section_result = (do { $config | get $section } | complete)
|
||
|
|
let section_data = if $section_result.exit_code == 0 { $section_result.stdout } else { {} }
|
||
|
|
let subsection_result = (do { $section_data | get $subsection } | complete)
|
||
|
|
let subsection_data = if $subsection_result.exit_code == 0 { $subsection_result.stdout } else { {} }
|
||
|
|
$config | upsert $section ($section_data | upsert $subsection ($subsection_data | upsert $key $value))
|
||
|
|
}
|
||
|
|
_ => {
|
||
|
|
# For deeper nesting, use recursive approach
|
||
|
|
set-config-value-recursive $config $path_parts $value
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Recursive helper for deep config value setting
|
||
|
|
def set-config-value-recursive [config: record, path_parts: list, value: any] {
|
||
|
|
if ($path_parts | length) == 1 {
|
||
|
|
$config | upsert ($path_parts | first) $value
|
||
|
|
} else {
|
||
|
|
let current_key = ($path_parts | first)
|
||
|
|
let remaining_parts = ($path_parts | skip 1)
|
||
|
|
let current_result = (do { $config | get $current_key } | complete)
|
||
|
|
let current_section = if $current_result.exit_code == 0 { $current_result.stdout } else { {} }
|
||
|
|
$config | upsert $current_key (set-config-value-recursive $current_section $remaining_parts $value)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Apply environment variable overrides to configuration
|
||
|
|
export def apply-environment-variable-overrides [config: record, debug = false] {
|
||
|
|
# Map of environment variables to config paths with type conversion
|
||
|
|
let env_mappings = {
|
||
|
|
"PROVISIONING_DEBUG": { path: "debug.enabled", type: "bool" },
|
||
|
|
"PROVISIONING_LOG_LEVEL": { path: "debug.log_level", type: "string" },
|
||
|
|
"PROVISIONING_NO_TERMINAL": { path: "debug.no_terminal", type: "bool" },
|
||
|
|
"PROVISIONING_CHECK": { path: "debug.check", type: "bool" },
|
||
|
|
"PROVISIONING_METADATA": { path: "debug.metadata", type: "bool" },
|
||
|
|
"PROVISIONING_OUTPUT_FORMAT": { path: "output.format", type: "string" },
|
||
|
|
"PROVISIONING_FILE_VIEWER": { path: "output.file_viewer", type: "string" },
|
||
|
|
"PROVISIONING_USE_SOPS": { path: "sops.use_sops", type: "bool" },
|
||
|
|
"PROVISIONING_PROVIDER": { path: "providers.default", type: "string" },
|
||
|
|
"PROVISIONING_WORKSPACE_PATH": { path: "paths.workspace", type: "string" },
|
||
|
|
"PROVISIONING_INFRA_PATH": { path: "paths.infra", type: "string" },
|
||
|
|
"PROVISIONING_SOPS": { path: "sops.config_path", type: "string" },
|
||
|
|
"PROVISIONING_KAGE": { path: "sops.age_key_file", type: "string" }
|
||
|
|
}
|
||
|
|
|
||
|
|
# Use reduce --fold to process all env mappings (Rule 3: no mutable variables)
|
||
|
|
$env_mappings | columns | reduce --fold $config {|env_var, result|
|
||
|
|
let env_result = (do { $env | get $env_var } | complete)
|
||
|
|
let env_value = if $env_result.exit_code == 0 { $env_result.stdout } else { null }
|
||
|
|
|
||
|
|
if ($env_value | is-not-empty) {
|
||
|
|
let mapping = ($env_mappings | get $env_var)
|
||
|
|
let config_path = $mapping.path
|
||
|
|
let config_type = $mapping.type
|
||
|
|
|
||
|
|
# Convert value to appropriate type
|
||
|
|
let converted_value = match $config_type {
|
||
|
|
"bool" => {
|
||
|
|
if ($env_value | describe) == "string" {
|
||
|
|
match ($env_value | str downcase) {
|
||
|
|
"true" | "1" | "yes" | "on" => true
|
||
|
|
"false" | "0" | "no" | "off" => false
|
||
|
|
_ => false
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
$env_value | into bool
|
||
|
|
}
|
||
|
|
}
|
||
|
|
"string" => $env_value
|
||
|
|
_ => $env_value
|
||
|
|
}
|
||
|
|
|
||
|
|
if $debug {
|
||
|
|
# log debug $"Applying env override: ($env_var) -> ($config_path) = ($converted_value)"
|
||
|
|
}
|
||
|
|
|
||
|
|
(set-config-value $result $config_path $converted_value)
|
||
|
|
} else {
|
||
|
|
$result
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|