Skip to content

Commit 64b640a

Browse files
Harden workflow inventory discovery
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 6ff3218 commit 64b640a

2 files changed

Lines changed: 204 additions & 26 deletions

File tree

.github/scripts/Get-ProcessPSModuleWorkflowInventory.ps1

Lines changed: 137 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -237,15 +237,29 @@ function Get-LocalDefaultBranch {
237237

238238
$originHead = (& git -C $RepositoryRoot symbolic-ref refs/remotes/origin/HEAD --short 2>$null) -join ''
239239
if ($LASTEXITCODE -eq 0 -and $originHead) {
240-
return $originHead -replace '^origin/', ''
240+
return [pscustomobject]@{
241+
Name = $originHead -replace '^origin/', ''
242+
Ref = $originHead
243+
}
244+
}
245+
246+
& git -C $RepositoryRoot show-ref --verify --quiet refs/heads/main
247+
if ($LASTEXITCODE -eq 0) {
248+
return [pscustomobject]@{
249+
Name = 'main'
250+
Ref = 'main'
251+
}
241252
}
242253

243254
$branch = (& git -C $RepositoryRoot branch --show-current 2>$null) -join ''
244255
if ($LASTEXITCODE -eq 0 -and $branch) {
245-
return $branch
256+
return [pscustomobject]@{
257+
Name = $branch
258+
Ref = $branch
259+
}
246260
}
247261

248-
$null
262+
throw "Could not determine a default or current branch for local repository [$RepositoryRoot]."
249263
}
250264

251265
function Get-LocalWorkflowFile {
@@ -255,26 +269,39 @@ function Get-LocalWorkflowFile {
255269
[string[]] $InputPath
256270
)
257271

272+
$seenRepositories = [Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
258273
foreach ($repositoryRoot in Get-LocalRepositoryRoot -InputPath $InputPath) {
259-
$workflowRoot = Join-Path $repositoryRoot '.github/workflows'
260-
if (-not (Test-Path -LiteralPath $workflowRoot -PathType Container)) {
274+
$repositoryName = Get-LocalRepositoryName -RepositoryRoot $repositoryRoot
275+
if (-not $seenRepositories.Add($repositoryName)) {
261276
continue
262277
}
263278

264-
$repositoryName = Get-LocalRepositoryName -RepositoryRoot $repositoryRoot
265279
$defaultBranch = Get-LocalDefaultBranch -RepositoryRoot $repositoryRoot
266-
Get-ChildItem -LiteralPath $workflowRoot -File |
267-
Where-Object { $_.Extension -in @('.yml', '.yaml') } |
280+
$workflowPaths = @(
281+
(& git -C $repositoryRoot ls-tree -r --name-only $defaultBranch.Ref -- '.github/workflows' 2>&1) -join "`n"
282+
)
283+
if ($LASTEXITCODE -ne 0) {
284+
throw "Could not list workflows from [$repositoryName] at [$($defaultBranch.Ref)]:`n$workflowPaths"
285+
}
286+
287+
@($workflowPaths -split '\r?\n') |
288+
Where-Object { [IO.Path]::GetExtension($_) -in @('.yml', '.yaml') } |
268289
ForEach-Object {
290+
$workflowPath = $_
291+
$content = (& git -C $repositoryRoot show "$($defaultBranch.Ref):$workflowPath" 2>&1) -join "`n"
292+
if ($LASTEXITCODE -ne 0) {
293+
throw "Could not read [$workflowPath] from [$repositoryName] at [$($defaultBranch.Ref)]:`n$content"
294+
}
295+
269296
[pscustomobject]@{
270297
Repository = $repositoryName
271-
DefaultBranch = $defaultBranch
298+
DefaultBranch = $defaultBranch.Name
272299
Archived = $false
273300
RepositoryUrl = $null
274-
WorkflowPath = [IO.Path]::GetRelativePath($repositoryRoot, $_.FullName).Replace('\', '/')
301+
WorkflowPath = $workflowPath
275302
WorkflowUrl = $null
276303
SearchQuery = $null
277-
Content = Get-Content -LiteralPath $_.FullName -Raw
304+
Content = $content
278305
}
279306
}
280307
}
@@ -318,7 +345,47 @@ function Get-MapValue {
318345
return $Map[$Name]
319346
}
320347

321-
$Map.PSObject.Properties[$Name].Value
348+
$property = $Map.PSObject.Properties[$Name]
349+
if ($null -eq $property) {
350+
return $null
351+
}
352+
353+
$property.Value
354+
}
355+
356+
function ConvertTo-TriggerMap {
357+
[CmdletBinding()]
358+
param(
359+
[Parameter()]
360+
[AllowNull()]
361+
[object] $Trigger
362+
)
363+
364+
if ($null -eq $Trigger) {
365+
return [ordered]@{}
366+
}
367+
368+
if ($Trigger -is [Collections.IDictionary]) {
369+
return $Trigger
370+
}
371+
372+
$result = [ordered]@{}
373+
if ($Trigger -is [string]) {
374+
$result[$Trigger] = $null
375+
return $result
376+
}
377+
378+
if ($Trigger -is [Collections.IEnumerable]) {
379+
foreach ($eventName in $Trigger) {
380+
if ($eventName -isnot [string]) {
381+
throw "Unsupported workflow trigger value type [$($eventName.GetType().FullName)]."
382+
}
383+
$result[$eventName] = $null
384+
}
385+
return $result
386+
}
387+
388+
throw "Unsupported workflow trigger type [$($Trigger.GetType().FullName)]."
322389
}
323390

324391
function ConvertTo-StringMap {
@@ -404,14 +471,29 @@ function Get-WorkflowInventoryItem {
404471
[ordered]@{}
405472
}
406473
Environment = Get-MapValue -Map $job -Name 'environment'
474+
Condition = Get-MapValue -Map $job -Name 'if'
407475
}
408476
}
409477

410478
if (-not $processJobs) {
411479
return
412480
}
413481

414-
$trigger = Get-MapValue -Map $workflow -Name 'on'
482+
try {
483+
$trigger = ConvertTo-TriggerMap -Trigger (Get-MapValue -Map $workflow -Name 'on')
484+
} catch {
485+
return [pscustomobject]@{
486+
Repository = $WorkflowFile.Repository
487+
DefaultBranch = $WorkflowFile.DefaultBranch
488+
Archived = $WorkflowFile.Archived
489+
RepositoryUrl = $WorkflowFile.RepositoryUrl
490+
WorkflowPath = $WorkflowFile.WorkflowPath
491+
WorkflowUrl = $WorkflowFile.WorkflowUrl
492+
SearchQuery = $WorkflowFile.SearchQuery
493+
Status = 'ParseError'
494+
Error = $_.Exception.Message
495+
}
496+
}
415497
$pullRequest = Get-MapValue -Map $trigger -Name 'pull_request'
416498
$push = Get-MapValue -Map $trigger -Name 'push'
417499
$schedule = Get-MapValue -Map $trigger -Name 'schedule'
@@ -442,6 +524,7 @@ function Get-WorkflowInventoryItem {
442524
Status = 'Parsed'
443525
Error = $null
444526
WorkflowName = Get-MapValue -Map $workflow -Name 'name'
527+
RunName = Get-MapValue -Map $workflow -Name 'run-name'
445528
Events = @(Get-MapKey -Map $trigger | Sort-Object)
446529
Schedules = @($schedule | ForEach-Object { Get-MapValue -Map $_ -Name 'cron' })
447530
PushBranches = ConvertTo-StringArray -Value (Get-MapValue -Map $push -Name 'branches')
@@ -493,6 +576,13 @@ function ConvertTo-WorkflowInventoryMarkdown {
493576
Group-Object |
494577
Sort-Object @{ Expression = 'Count'; Descending = $true }, Name
495578
)
579+
$versions = @(
580+
$parsed |
581+
ForEach-Object { $_.VersionComments.Version } |
582+
Where-Object { $_ } |
583+
Group-Object |
584+
Sort-Object @{ Expression = 'Count'; Descending = $true }, Name
585+
)
496586
$eventSets = @(
497587
$parsed |
498588
ForEach-Object { $_.Events -join ', ' } |
@@ -501,6 +591,11 @@ function ConvertTo-WorkflowInventoryMarkdown {
501591
)
502592

503593
$lines = [Collections.Generic.List[string]]::new()
594+
$lines.Add('---')
595+
$lines.Add('title: Process-PSModule workflow fleet inventory')
596+
$lines.Add('description: Generated inventory of PSModule repositories that call the Process-PSModule reusable workflow.')
597+
$lines.Add('---')
598+
$lines.Add('')
504599
$lines.Add('# Process-PSModule workflow inventory')
505600
$lines.Add('')
506601
$lines.Add("Generated: $(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssK')")
@@ -518,6 +613,14 @@ function ConvertTo-WorkflowInventoryMarkdown {
518613
$lines.Add("| $(ConvertTo-MarkdownCell $group.Name) | $($group.Count) |")
519614
}
520615
$lines.Add('')
616+
$lines.Add('## Version distribution')
617+
$lines.Add('')
618+
$lines.Add('| Version comment | Workflows |')
619+
$lines.Add('| --- | ---: |')
620+
foreach ($group in $versions) {
621+
$lines.Add("| $(ConvertTo-MarkdownCell $group.Name) | $($group.Count) |")
622+
}
623+
$lines.Add('')
521624
$lines.Add('## Trigger distribution')
522625
$lines.Add('')
523626
$lines.Add('| Events | Workflows |')
@@ -529,18 +632,29 @@ function ConvertTo-WorkflowInventoryMarkdown {
529632
$lines.Add('## Workflow files')
530633
$lines.Add('')
531634
$lines.Add(
532-
'| Repository | File | Name | Events | Reference | Version | PR types | Push branches | Schedule |' +
533-
' Concurrency | Cancel | Permissions | Secrets | Inputs | Extra jobs |'
635+
'| Repository | File | Name | Run name | Events | Reference | Version | PR types | Push branches | Schedule |' +
636+
' Concurrency | Cancel | Permissions | Condition | Secrets | Inputs | Extra jobs |'
534637
)
535638
$lines.Add(
536-
'| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |'
639+
'| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |'
537640
)
538641

539642
foreach ($item in $Inventory | Sort-Object Repository, WorkflowPath) {
643+
$repositoryCell = if ($item.RepositoryUrl) {
644+
"[$($item.Repository)]($($item.RepositoryUrl))"
645+
} else {
646+
$item.Repository
647+
}
648+
$workflowCell = if ($item.WorkflowUrl) {
649+
"[$($item.WorkflowPath)]($($item.WorkflowUrl))"
650+
} else {
651+
$item.WorkflowPath
652+
}
653+
540654
if ($item.Status -eq 'ParseError') {
541655
$lines.Add(
542-
"| $(ConvertTo-MarkdownCell $item.Repository) " +
543-
"| $(ConvertTo-MarkdownCell $item.WorkflowPath) | parse error | | | | | | | | | | | | |"
656+
"| $(ConvertTo-MarkdownCell $repositoryCell) " +
657+
"| $(ConvertTo-MarkdownCell $workflowCell) | parse error | | | | | | | | | | | | | | |"
544658
)
545659
continue
546660
}
@@ -561,16 +675,18 @@ function ConvertTo-WorkflowInventoryMarkdown {
561675
ForEach-Object { $_.Inputs.Keys } |
562676
Sort-Object -Unique
563677
)
678+
$conditionSummary = @($item.ProcessJobs.Condition | Where-Object { $_ } | Sort-Object -Unique)
564679
$permissionSummary = @(
565680
$item.Permissions.GetEnumerator() |
566681
Sort-Object Key |
567682
ForEach-Object { "$($_.Key)=$($_.Value)" }
568683
)
569684

570685
$lines.Add(
571-
"| $(ConvertTo-MarkdownCell $item.Repository) " +
572-
"| $(ConvertTo-MarkdownCell $item.WorkflowPath) " +
686+
"| $(ConvertTo-MarkdownCell $repositoryCell) " +
687+
"| $(ConvertTo-MarkdownCell $workflowCell) " +
573688
"| $(ConvertTo-MarkdownCell $item.WorkflowName) " +
689+
"| $(ConvertTo-MarkdownCell $item.RunName) " +
574690
"| $(ConvertTo-MarkdownCell $item.Events) " +
575691
"| $(ConvertTo-MarkdownCell $referencesForItem) " +
576692
"| $(ConvertTo-MarkdownCell $versionsForItem) " +
@@ -580,6 +696,7 @@ function ConvertTo-WorkflowInventoryMarkdown {
580696
"| $(ConvertTo-MarkdownCell $item.ConcurrencyGroup) " +
581697
"| $(ConvertTo-MarkdownCell $item.CancelInProgress) " +
582698
"| $(ConvertTo-MarkdownCell $permissionSummary) " +
699+
"| $(ConvertTo-MarkdownCell $conditionSummary) " +
583700
"| $(ConvertTo-MarkdownCell $secretSummary) " +
584701
"| $(ConvertTo-MarkdownCell $inputSummary) " +
585702
"| $(ConvertTo-MarkdownCell $item.AdditionalJobs) |"

0 commit comments

Comments
 (0)