120 lines
4.0 KiB
PowerShell
120 lines
4.0 KiB
PowerShell
#requires -Version 7.0
|
|
#requires -PSEdition Core
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Quality gate plugin for validating release readiness.
|
|
|
|
.DESCRIPTION
|
|
This plugin evaluates quality constraints using shared test
|
|
results and project files. It enforces coverage thresholds
|
|
and checks for vulnerable packages before release plugins run.
|
|
#>
|
|
|
|
if (-not (Get-Command Import-PluginDependency -ErrorAction SilentlyContinue)) {
|
|
$pluginSupportModulePath = Join-Path (Split-Path $PSScriptRoot -Parent) "PluginSupport.psm1"
|
|
if (Test-Path $pluginSupportModulePath -PathType Leaf) {
|
|
Import-Module $pluginSupportModulePath -Force -Global -ErrorAction Stop
|
|
}
|
|
}
|
|
|
|
function Test-VulnerablePackagesInternal {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string[]]$ProjectFiles
|
|
)
|
|
|
|
$findings = @()
|
|
|
|
foreach ($projectPath in $ProjectFiles) {
|
|
Write-Log -Level "STEP" -Message "Checking vulnerable packages: $([System.IO.Path]::GetFileName($projectPath))"
|
|
|
|
$output = & dotnet list $projectPath package --vulnerable --include-transitive 2>&1
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "dotnet list package --vulnerable failed for $projectPath."
|
|
}
|
|
|
|
$outputText = ($output | Out-String)
|
|
if ($outputText -match "(?im)\bhas the following vulnerable packages\b" -or $outputText -match "(?im)^\s*>\s+[A-Za-z0-9_.-]+\s") {
|
|
$findings += [pscustomobject]@{
|
|
Project = $projectPath
|
|
Output = $outputText.Trim()
|
|
}
|
|
}
|
|
}
|
|
|
|
return $findings
|
|
}
|
|
|
|
function Invoke-Plugin {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
$Settings
|
|
)
|
|
|
|
Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log"
|
|
Import-PluginDependency -ModuleName "ScriptConfig" -RequiredCommand "Assert-Command"
|
|
|
|
$pluginSettings = $Settings
|
|
$sharedSettings = $Settings.Context
|
|
$coverageThresholdSetting = $pluginSettings.coverageThreshold
|
|
$failOnVulnerabilitiesSetting = $pluginSettings.failOnVulnerabilities
|
|
$projectFiles = $sharedSettings.ProjectFiles
|
|
$testResult = $null
|
|
if ($sharedSettings.PSObject.Properties['TestResult']) {
|
|
$testResult = $sharedSettings.TestResult
|
|
}
|
|
|
|
if ($null -eq $testResult) {
|
|
throw "QualityGate plugin requires test results. Run the DotNetTest plugin first."
|
|
}
|
|
|
|
$coverageThreshold = 0
|
|
if ($null -ne $coverageThresholdSetting) {
|
|
$coverageThreshold = [double]$coverageThresholdSetting
|
|
}
|
|
|
|
if ($coverageThreshold -gt 0) {
|
|
Write-Log -Level "STEP" -Message "Checking coverage threshold..."
|
|
if ([double]$testResult.LineRate -lt $coverageThreshold) {
|
|
throw "Line coverage $($testResult.LineRate)% is below the configured threshold of $coverageThreshold%."
|
|
}
|
|
|
|
Write-Log -Level "OK" -Message " Coverage threshold met: $($testResult.LineRate)% >= $coverageThreshold%"
|
|
}
|
|
else {
|
|
Write-Log -Level "WARN" -Message "Skipping coverage threshold check (disabled)."
|
|
}
|
|
|
|
Assert-Command dotnet
|
|
|
|
$failOnVulnerabilities = $true
|
|
if ($null -ne $failOnVulnerabilitiesSetting) {
|
|
$failOnVulnerabilities = [bool]$failOnVulnerabilitiesSetting
|
|
}
|
|
|
|
$vulnerabilities = Test-VulnerablePackagesInternal -ProjectFiles $projectFiles
|
|
|
|
if ($vulnerabilities.Count -eq 0) {
|
|
Write-Log -Level "OK" -Message " No vulnerable packages detected."
|
|
return
|
|
}
|
|
|
|
foreach ($finding in $vulnerabilities) {
|
|
Write-Log -Level "WARN" -Message " Vulnerable packages detected in $([System.IO.Path]::GetFileName($finding.Project))"
|
|
$finding.Output -split "`r?`n" | ForEach-Object {
|
|
if (-not [string]::IsNullOrWhiteSpace($_)) {
|
|
Write-Log -Level "WARN" -Message " $_"
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($failOnVulnerabilities) {
|
|
throw "Vulnerable packages were detected and failOnVulnerabilities is enabled."
|
|
}
|
|
|
|
Write-Log -Level "WARN" -Message "Vulnerable packages detected, but failOnVulnerabilities is disabled."
|
|
}
|
|
|
|
Export-ModuleMember -Function Invoke-Plugin
|