Modules/businessdev.ALbuild.Pipeline/Private/Get-AdoPipelineStepList.ps1
|
function Get-AdoPipelineStepList { <# .SYNOPSIS Flattens a parsed Azure DevOps pipeline document into an ordered list of executable steps. .DESCRIPTION Internal helper for the local pipeline runner. Walks stages -> jobs -> steps (and a deployment job's strategy.runOnce.deploy.steps), resolving compile-time "${{ if }}" conditional insertion and "${{ each x in <list> }}" loops using the supplied parameters. Only the constructs used by the ALbuild templates are supported. Each returned item carries the raw step mapping and the parameter/loop context to expand it with at run time. .PARAMETER Document The parsed pipeline (an ordered dictionary from ConvertFrom-Yaml -Ordered). .PARAMETER Parameters Resolved template parameters (name -> value). .OUTPUTS PSCustomObject[] each with: Step (IDictionary) and Parameters (hashtable). #> [CmdletBinding()] [OutputType([PSCustomObject[]])] param( [Parameter(Mandatory)] [System.Collections.IDictionary] $Document, [hashtable] $Parameters = @{} ) $result = [System.Collections.Generic.List[object]]::new() function Test-IsMap([object] $o) { return ($o -is [System.Collections.IDictionary]) } function Test-IsSeq([object] $o) { return ($o -is [System.Collections.IEnumerable] -and $o -isnot [string] -and -not (Test-IsMap $o)) } function Get-Insertion([System.Collections.IDictionary] $map) { # A conditional/loop insertion is a single-key map whose key is a "${{ if|each ... }}" directive. $keys = @($map.Keys) if ($keys.Count -ne 1) { return $null } $key = "$($keys[0])" $ifm = [regex]::Match($key, '^\$\{\{\s*if\s+(?<cond>.+?)\s*\}\}$') if ($ifm.Success) { return [PSCustomObject]@{ Kind = 'if'; Cond = $ifm.Groups['cond'].Value; Body = $map[$keys[0]] } } $eachm = [regex]::Match($key, '^\$\{\{\s*each\s+(?<var>[A-Za-z_]\w*)\s+in\s+(?<list>.+?)\s*\}\}$') if ($eachm.Success) { return [PSCustomObject]@{ Kind = 'each'; Var = $eachm.Groups['var'].Value; ListExpr = $eachm.Groups['list'].Value; Body = $map[$keys[0]] } } return $null } function Copy-Parameter([hashtable] $p, [string] $name, [object] $value) { $clone = @{}; foreach ($k in $p.Keys) { $clone[$k] = $p[$k] } if ($name) { $clone[$name] = $value } return $clone } # Generic sequence walker: calls $onItem for each non-insertion item; expands if/each. function Expand-Sequence([object] $seq, [hashtable] $p, [scriptblock] $onItem) { if (-not (Test-IsSeq $seq)) { return } foreach ($item in $seq) { if (Test-IsMap $item) { $ins = Get-Insertion $item if ($ins) { if ($ins.Kind -eq 'if') { if (Test-AdoCondition -Condition $ins.Cond -Parameters $p) { Expand-Sequence $ins.Body $p $onItem } } else { $list = Get-AdoExpressionValue -Expr $ins.ListExpr -Parameters $p foreach ($val in @($list)) { Expand-Sequence $ins.Body (Copy-Parameter $p $ins.Var $val) $onItem } } continue } } & $onItem $item $p } } $addStep = { param($step, $p) $result.Add([PSCustomObject]@{ Step = $step; Parameters = $p }) } $walkJob = { param($job, $p) if (-not (Test-IsMap $job)) { return } if ($job.Contains('steps')) { Expand-Sequence $job['steps'] $p $addStep } elseif ($job.Contains('strategy')) { $deploySteps = $null try { $deploySteps = $job['strategy']['runOnce']['deploy']['steps'] } catch { $deploySteps = $null } if ($deploySteps) { Expand-Sequence $deploySteps $p $addStep } } } $walkStage = { param($stage, $p) if ((Test-IsMap $stage) -and $stage.Contains('jobs')) { Expand-Sequence $stage['jobs'] $p $walkJob } } if ($Document.Contains('steps')) { Expand-Sequence $Document['steps'] $Parameters $addStep } elseif ($Document.Contains('jobs')) { Expand-Sequence $Document['jobs'] $Parameters $walkJob } elseif ($Document.Contains('stages')) { Expand-Sequence $Document['stages'] $Parameters $walkStage } return $result.ToArray() } |