303 lines
9.3 KiB
Text
303 lines
9.3 KiB
Text
# Final Configuration Cache System
|
|
# Caches fully merged and validated configuration
|
|
# Uses AGGRESSIVE validation: checks ALL source files on cache hit
|
|
# TTL: 5 minutes (short for safety - workspace configs can change)
|
|
# Follows Nushell 0.109.0+ guidelines
|
|
|
|
# Selective imports (ADR-025 Phase 3 Layer 2).
|
|
# cache/metadata star-import was dead — dropped.
|
|
use domain/cache/core.nu [cache-clear-type cache-lookup cache-write]
|
|
|
|
# Helper: Generate cache key for workspace + environment combination
|
|
def compute-final-config-key [
|
|
workspace: record # { name: "librecloud", ... }
|
|
environment: string # "dev", "test", "prod"
|
|
] {
|
|
let combined = $"($workspace.name)-($environment)"
|
|
let hash_result = (do {
|
|
$combined | ^openssl dgst -sha256 -hex
|
|
} | complete)
|
|
|
|
if $hash_result.exit_code == 0 {
|
|
($hash_result.stdout | str trim | split column " " | get column1 | get 0)
|
|
} else {
|
|
($combined | hash md5 | str substring 0..32)
|
|
}
|
|
}
|
|
|
|
# Helper: Get all configuration source files for aggressive validation
|
|
def get-all-source-files [
|
|
workspace: record
|
|
] {
|
|
mut source_files = []
|
|
|
|
# Workspace config files
|
|
let config_dir = ($workspace.path | path join "config")
|
|
if ($config_dir | path exists) {
|
|
# Add main config files
|
|
for config_file in ["provisioning.ncl" "provisioning.yaml"] {
|
|
let file_path = ($config_dir | path join $config_file)
|
|
if ($file_path | path exists) {
|
|
$source_files = ($source_files | append $file_path)
|
|
}
|
|
}
|
|
|
|
# Add provider configs
|
|
let providers_dir = ($config_dir | path join "providers")
|
|
if ($providers_dir | path exists) {
|
|
for provider_file in (glob $"($providers_dir)/**/*.toml") {
|
|
$source_files = ($source_files | append $provider_file)
|
|
}
|
|
}
|
|
|
|
# Add platform configs
|
|
let platform_dir = ($config_dir | path join "platform")
|
|
if ($platform_dir | path exists) {
|
|
for platform_file in (glob $"($platform_dir)/**/*.toml") {
|
|
$source_files = ($source_files | append $platform_file)
|
|
}
|
|
}
|
|
}
|
|
|
|
$source_files
|
|
}
|
|
|
|
# ============================================================================
|
|
# PUBLIC API: Final Config Cache Operations
|
|
# ============================================================================
|
|
|
|
# Cache final merged configuration
|
|
export def cache-final-config [
|
|
config: record # Fully merged configuration
|
|
workspace: record # Workspace info
|
|
environment: string # Environment name
|
|
] {
|
|
let cache_key = (compute-final-config-key $workspace $environment)
|
|
let source_files = (get-all-source-files $workspace)
|
|
|
|
# Write cache with 5-minute TTL (aggressive - short for safety)
|
|
cache-write "final" $cache_key $config $source_files --ttl 300
|
|
}
|
|
|
|
# Lookup final config cache with AGGRESSIVE validation
|
|
export def lookup-final-config [
|
|
workspace: record
|
|
environment: string
|
|
] {
|
|
let cache_key = (compute-final-config-key $workspace $environment)
|
|
|
|
# Try to lookup in cache
|
|
let cache_result = (cache-lookup "final" $cache_key)
|
|
|
|
if not $cache_result.valid {
|
|
return {
|
|
valid: false,
|
|
reason: $cache_result.reason,
|
|
data: null
|
|
}
|
|
}
|
|
|
|
# AGGRESSIVE VALIDATION: Check ALL source files
|
|
let source_files = (get-all-source-files $workspace)
|
|
|
|
for src_file in $source_files {
|
|
if not ($src_file | path exists) {
|
|
# Source file was deleted - invalidate cache
|
|
return {
|
|
valid: false,
|
|
reason: "source_file_deleted",
|
|
data: null
|
|
}
|
|
}
|
|
|
|
# Check modification time
|
|
let file_dir = ($src_file | path dirname)
|
|
let file_name = ($src_file | path basename)
|
|
let file_list = (ls $file_dir | where name == $file_name)
|
|
|
|
if ($file_list | length) > 0 {
|
|
let file_info = ($file_list | get 0)
|
|
let current_mtime = ($file_info.modified | into int)
|
|
# mtime check happens later in the validation
|
|
} else {
|
|
return {
|
|
valid: false,
|
|
reason: "cannot_stat_source_file",
|
|
data: null
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
valid: true,
|
|
reason: "cache_hit_aggressive_validated",
|
|
data: $cache_result.data
|
|
}
|
|
}
|
|
|
|
# Force invalidation of final config cache
|
|
export def invalidate-final-cache [
|
|
workspace: string
|
|
environment: string = "*" # "*" = all environments
|
|
] {
|
|
if $environment == "*" {
|
|
# Invalidate ALL environments for workspace
|
|
let base = (let home = ($env.HOME? | default "~" | path expand);
|
|
$home | path join ".provisioning" "cache" "config" "workspaces")
|
|
|
|
if ($base | path exists) {
|
|
# Find and delete all cache files for this workspace
|
|
for cache_file in (glob $"($base)/*") {
|
|
if ($cache_file | str contains $workspace) {
|
|
do {
|
|
rm -f $cache_file
|
|
rm -f $"($cache_file).meta"
|
|
} | complete | ignore
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
# Invalidate specific environment
|
|
let workspace_rec = { name: $workspace, path: "" }
|
|
let cache_key = (compute-final-config-key $workspace_rec $environment)
|
|
|
|
let home = ($env.HOME? | default "~" | path expand)
|
|
let cache_file = ($home | path join ".provisioning" "cache" "config" "workspaces" $cache_key)
|
|
|
|
do {
|
|
rm -f $cache_file
|
|
rm -f $"($cache_file).meta"
|
|
} | complete | ignore
|
|
}
|
|
}
|
|
|
|
# Pre-populate cache (warm it)
|
|
export def warm-cache [
|
|
workspace: record
|
|
environment: string
|
|
] {
|
|
# This function would be called AFTER full config is loaded
|
|
# but BEFORE we return from load-provisioning-config
|
|
# Keeping it as placeholder for proper flow
|
|
}
|
|
|
|
# Validate final config cache
|
|
def validate-final-config [
|
|
cache_file: string
|
|
meta_file: string
|
|
] {
|
|
# Load metadata
|
|
let meta_load = (do {
|
|
open $meta_file
|
|
} | complete)
|
|
|
|
if $meta_load.exit_code != 0 {
|
|
return { valid: false, reason: "metadata_not_found" }
|
|
}
|
|
|
|
let meta = $meta_load.stdout
|
|
|
|
# Check TTL (aggressive - short TTL of 5 minutes)
|
|
let now = (date now | format date "%Y-%m-%dT%H:%M:%SZ")
|
|
if $now > $meta.expires_at {
|
|
return { valid: false, reason: "ttl_expired" }
|
|
}
|
|
|
|
# Check ALL source file mtimes
|
|
mut all_valid = true
|
|
for src_file in $meta.source_files {
|
|
if not ($src_file | path exists) {
|
|
$all_valid = false
|
|
break
|
|
}
|
|
|
|
let current_mtime = (do {
|
|
$src_file | stat | get modified | into int
|
|
} | complete)
|
|
|
|
if $current_mtime.exit_code != 0 {
|
|
$all_valid = false
|
|
break
|
|
}
|
|
|
|
let cached_mtime = ($meta.source_mtimes | get --optional $src_file | default (-1))
|
|
|
|
if $current_mtime.stdout != $cached_mtime {
|
|
$all_valid = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if $all_valid {
|
|
{ valid: true, reason: "aggressive_validation_passed" }
|
|
} else {
|
|
{ valid: false, reason: "source_file_mtime_mismatch" }
|
|
}
|
|
}
|
|
|
|
# Get final config cache statistics
|
|
export def get-final-cache-stats [] {
|
|
let home = ($env.HOME? | default "~" | path expand)
|
|
let base = ($home | path join ".provisioning" "cache" "config" "workspaces")
|
|
|
|
if not ($base | path exists) {
|
|
return {
|
|
total_entries: 0,
|
|
total_size_mb: 0,
|
|
workspaces: []
|
|
}
|
|
}
|
|
|
|
mut stats = {
|
|
total_entries: 0,
|
|
total_size_mb: 0,
|
|
workspaces: []
|
|
}
|
|
|
|
mut workspace_map = {}
|
|
|
|
for meta_file in (glob $"($base)/**/*.meta") {
|
|
let cache_file = ($meta_file | str substring 0..-6)
|
|
|
|
if ($cache_file | path exists) {
|
|
let size_result = (do {
|
|
$cache_file | stat | get size
|
|
} | complete)
|
|
|
|
if $size_result.exit_code == 0 {
|
|
let size_mb = ($size_result.stdout / 1048576)
|
|
$stats.total_entries += 1
|
|
$stats.total_size_mb += $size_mb
|
|
|
|
# Extract workspace name from cache file
|
|
let filename = ($cache_file | path basename)
|
|
let workspace_name = ($filename | str substring 0..($filename | str index-of "-" | default 0))
|
|
|
|
if ($workspace_map | has -c $workspace_name) {
|
|
let ws_stat = ($workspace_map | get $workspace_name)
|
|
let updated_ws = {
|
|
entries: ($ws_stat.entries + 1),
|
|
size_mb: ($ws_stat.size_mb + $size_mb)
|
|
}
|
|
$workspace_map = ($workspace_map | insert $workspace_name $updated_ws)
|
|
} else {
|
|
$workspace_map = ($workspace_map | insert $workspace_name { entries: 1, size_mb: $size_mb })
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$stats = ($stats | insert workspaces ($workspace_map | to json | from json))
|
|
$stats
|
|
}
|
|
|
|
# Clear final config cache for specific workspace
|
|
export def clear-final-cache [
|
|
workspace: string = "" # "" = clear all
|
|
] {
|
|
if ($workspace | is-empty) {
|
|
cache-clear-type "final"
|
|
} else {
|
|
invalidate-final-cache $workspace "*"
|
|
}
|
|
}
|