plugins/bob/public/Resolve-JaxRunConfigImports.ps1
|
function Resolve-JaxRunConfigImports { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.Collections.IDictionary] $Config, [Parameter(Mandatory = $true)] [string] $ConfigPath, [string] $RepoRoot, [scriptblock] $YamlReader, [int] $Depth = 0, [System.Collections.Generic.List[string]] $ImportStack, [System.Collections.Generic.Dictionary[string, object]] $ResolvedCache, [ref] $UsedPaths ) if ($Depth -gt 50) { throw "Run-config import recursion exceeded 50 levels at '$ConfigPath'." } if ($null -eq $ImportStack) { $ImportStack = [System.Collections.Generic.List[string]]::new() } $useCache = $true if ($null -ne $UsedPaths) { # When collecting provenance paths, do not short-circuit via cache; we need full traversal to gather all imports. $useCache = $false $ResolvedCache = $null } elseif ($null -eq $ResolvedCache) { $ResolvedCache = New-Object 'System.Collections.Generic.Dictionary[string, object]' ([System.StringComparer]::OrdinalIgnoreCase) } $fullConfigPath = (Resolve-Path $ConfigPath).Path if ($useCache -and $ResolvedCache.ContainsKey($fullConfigPath)) { Write-Debug ("Resolve-JaxRunConfigImports cache hit: '{0}'" -f $fullConfigPath) if ($null -ne $UsedPaths) { $UsedPaths.Value += $fullConfigPath } return $ResolvedCache[$fullConfigPath] } if ($null -ne $UsedPaths) { $UsedPaths.Value += $fullConfigPath } $cycleIndex = $ImportStack.IndexOf($fullConfigPath) if ($cycleIndex -ge 0) { $cycle = @($ImportStack[$cycleIndex..($ImportStack.Count - 1)]) + @($fullConfigPath) $cycleText = $cycle -join ' -> ' throw "Run-config import cycle detected: $cycleText" } $ImportStack.Add($fullConfigPath) | Out-Null Write-Debug ("Resolve-JaxRunConfigImports start (depth={0}): '{1}'" -f $Depth, $fullConfigPath) $imports = $null if ($Config.Keys -contains 'import') { $imports = $Config['import'] } if ($null -eq $imports) { $ImportStack.RemoveAt($ImportStack.Count - 1) if ($useCache) { $ResolvedCache[$fullConfigPath] = $Config } return $Config } $importList = @() if ($imports -is [string]) { $importList = @($imports) } elseif ($imports -is [System.Collections.IEnumerable]) { $importList = @($imports) } $current = @{} foreach ($key in $Config.Keys) { if ($key -eq 'import') { continue } $current[$key] = $Config[$key] } $baseDir = Split-Path -Path $fullConfigPath -Parent $reader = $YamlReader if ($null -eq $reader) { $reader = { param($path) Read-JaxYaml -Path $path } } foreach ($importEntry in $importList) { if ([string]::IsNullOrWhiteSpace($importEntry)) { continue } $resolved = Resolve-JaxRunConfigPath -Path $importEntry -RepoRoot $RepoRoot -BaseDir $baseDir if (-not (Test-Path -Path $resolved -PathType Leaf)) { throw "Run-config import not found: '$resolved' (from '$fullConfigPath')." } Write-Verbose ("Run-config import: '{0}' -> '{1}'" -f $importEntry, $resolved) $importConfig = & $reader $resolved if ($null -eq $importConfig) { continue } if ($importConfig -isnot [System.Collections.IDictionary]) { throw "Run-config import '$resolved' did not return a map/object." } if ($null -ne $UsedPaths) { $importConfig = Resolve-JaxRunConfigImports -Config $importConfig -ConfigPath $resolved -RepoRoot $RepoRoot -YamlReader $reader -Depth ($Depth + 1) -ImportStack $ImportStack -ResolvedCache $ResolvedCache -UsedPaths $UsedPaths } else { $importConfig = Resolve-JaxRunConfigImports -Config $importConfig -ConfigPath $resolved -RepoRoot $RepoRoot -YamlReader $reader -Depth ($Depth + 1) -ImportStack $ImportStack -ResolvedCache $ResolvedCache } $current = Merge-JaxHashtable -Base $importConfig -Overlay $current } $ImportStack.RemoveAt($ImportStack.Count - 1) if ($useCache) { $ResolvedCache[$fullConfigPath] = $current } return $current } |