maksit-results/utils/modules/Engine/EngineContext.psm1

333 lines
8.5 KiB
PowerShell

#requires -Version 7.0
#requires -PSEdition Core
<#
.SYNOPSIS
Generic engine helpers: path resolution and the shared context facts API.
.DESCRIPTION
Engine-owned state stays on the context object (version, tag, skipPublishPlugins, …).
Plugin-published values go under $context.facts[namespace][name] via Set/Get/Test-EngineFact.
During migration, -LegacyProperty on Set dual-writes flat properties; Get falls back to them.
Version plugins declare providesVersion = $true; New-EngineContext discovers the single enabled one.
#>
if (-not (Get-Command Write-Log -ErrorAction SilentlyContinue)) {
$loggingModulePath = Join-Path (Split-Path $PSScriptRoot -Parent) "Logging.psm1"
if (Test-Path $loggingModulePath -PathType Leaf) {
Import-Module $loggingModulePath -Force
}
}
$script:EngineStateAllowlist = @(
'version'
'tag'
'skipPublishPlugins'
'releaseDir'
'artifactsDirectory'
'deployMode'
'orchestrator'
)
function Resolve-RelativePaths {
param(
[Parameter(Mandatory = $true)]
[object]$Value,
[Parameter(Mandatory = $true)]
[string]$BasePath
)
if ($null -eq $Value) {
return @()
}
$rawPaths = @()
if ($Value -is [System.Collections.IEnumerable] -and -not ($Value -is [string])) {
$rawPaths += $Value
}
else {
$rawPaths += $Value
}
$resolved = @()
foreach ($p in $rawPaths) {
if ([string]::IsNullOrWhiteSpace([string]$p)) {
continue
}
$resolved += [System.IO.Path]::GetFullPath((Join-Path $BasePath ([string]$p)))
}
return @($resolved)
}
function Initialize-EngineFactsBag {
param(
[Parameter(Mandatory = $true)]
[psobject]$Context
)
if (-not ($Context.PSObject.Properties.Name -contains 'facts') -or $null -eq $Context.facts) {
$Context | Add-Member -NotePropertyName facts -NotePropertyValue ([ordered]@{}) -Force
}
}
function Assert-EngineFactNamespace {
param(
[Parameter(Mandatory = $true)]
[string]$Namespace
)
if ($Namespace -eq 'engine') {
throw "Namespace 'engine' is reserved; use Set-EngineState / Get-EngineState for engine-owned fields."
}
if ($Namespace -cnotmatch '^[a-z][a-z0-9]*$') {
throw "Invalid facts namespace '$Namespace'. Use lowercase alphanumeric starting with a letter (e.g. 'test', 'dotnet')."
}
}
function Assert-EngineFactName {
param(
[Parameter(Mandatory = $true)]
[string]$Name
)
if ($Name -cnotmatch '^[a-zA-Z][a-zA-Z0-9_]*$') {
throw "Invalid fact name '$Name'. Use letters, digits, underscore; must start with a letter."
}
}
function Test-EngineFactValuePresent {
param(
[AllowNull()]
$Value
)
if ($null -eq $Value) {
return $false
}
if ($Value -is [string] -and [string]::IsNullOrWhiteSpace($Value)) {
return $false
}
return $true
}
function Set-EngineFact {
param(
[Parameter(Mandatory = $true)]
[psobject]$Context,
[Parameter(Mandatory = $true)]
[string]$Namespace,
[Parameter(Mandatory = $true)]
[string]$Name,
[Parameter(Mandatory = $true)]
[AllowNull()]
$Value,
[ValidateSet('Error', 'Replace', 'Keep')]
[string]$Overwrite = 'Error',
[Parameter(Mandatory = $false)]
[string]$LegacyProperty
)
Assert-EngineFactNamespace -Namespace $Namespace
Assert-EngineFactName -Name $Name
Initialize-EngineFactsBag -Context $Context
if (-not $Context.facts.Contains($Namespace)) {
$Context.facts[$Namespace] = [ordered]@{}
}
$bag = $Context.facts[$Namespace]
$exists = $bag.Contains($Name)
if ($exists) {
if ($Overwrite -eq 'Keep') {
if (-not [string]::IsNullOrWhiteSpace($LegacyProperty) -and -not ($Context.PSObject.Properties.Name -contains $LegacyProperty)) {
$Context | Add-Member -NotePropertyName $LegacyProperty -NotePropertyValue $bag[$Name] -Force
}
return
}
if ($Overwrite -eq 'Error') {
throw "Fact ${Namespace}.${Name} already set (use -Overwrite Replace or Keep)."
}
}
$bag[$Name] = $Value
if (-not [string]::IsNullOrWhiteSpace($LegacyProperty)) {
$Context | Add-Member -NotePropertyName $LegacyProperty -NotePropertyValue $Value -Force
}
}
function Get-EngineFact {
param(
[Parameter(Mandatory = $true)]
[psobject]$Context,
[Parameter(Mandatory = $true)]
[string]$Namespace,
[Parameter(Mandatory = $true)]
[string]$Name,
[Parameter(Mandatory = $false)]
$Default,
[switch]$Required,
[Parameter(Mandatory = $false)]
[string[]]$LegacyProperty
)
Assert-EngineFactNamespace -Namespace $Namespace
Assert-EngineFactName -Name $Name
Initialize-EngineFactsBag -Context $Context
if ($Context.facts.Contains($Namespace) -and $Context.facts[$Namespace].Contains($Name)) {
$value = $Context.facts[$Namespace][$Name]
if (Test-EngineFactValuePresent -Value $value) {
return $value
}
}
foreach ($propertyName in @($LegacyProperty)) {
if ([string]::IsNullOrWhiteSpace($propertyName)) {
continue
}
if ($Context.PSObject.Properties.Name -contains $propertyName) {
$legacyValue = $Context.$propertyName
if (Test-EngineFactValuePresent -Value $legacyValue) {
return $legacyValue
}
}
}
if ($Required) {
throw "Required fact ${Namespace}.${Name} is missing."
}
if ($PSBoundParameters.ContainsKey('Default')) {
return $Default
}
return $null
}
function Test-EngineFact {
param(
[Parameter(Mandatory = $true)]
[psobject]$Context,
[Parameter(Mandatory = $true)]
[string]$Namespace,
[Parameter(Mandatory = $true)]
[string]$Name,
[Parameter(Mandatory = $false)]
[string[]]$LegacyProperty
)
$value = Get-EngineFact -Context $Context -Namespace $Namespace -Name $Name -LegacyProperty $LegacyProperty
return (Test-EngineFactValuePresent -Value $value)
}
function Set-EngineState {
param(
[Parameter(Mandatory = $true)]
[psobject]$Context,
[Parameter(Mandatory = $true)]
[string]$Name,
[Parameter(Mandatory = $true)]
[AllowNull()]
$Value
)
if ($script:EngineStateAllowlist -notcontains $Name) {
throw "Engine state key '$Name' is not allowlisted. Use Set-EngineFact for plugin outputs, or extend the engine allowlist for documented engine fields."
}
$Context | Add-Member -NotePropertyName $Name -NotePropertyValue $Value -Force
}
function Add-EnginePublishCompletion {
param(
[Parameter(Mandatory = $true)]
[psobject]$Context,
[Parameter(Mandatory = $true)]
[string]$Publisher
)
if ([string]::IsNullOrWhiteSpace($Publisher)) {
throw "Publisher name is required."
}
$completedBy = @(Get-EngineFact -Context $Context -Namespace 'publish' -Name 'completedBy' -Default @())
if ($completedBy -notcontains $Publisher) {
$completedBy += $Publisher
}
Set-EngineFact -Context $Context -Namespace 'publish' -Name 'completedBy' -Value $completedBy -Overwrite Replace
Set-EngineFact -Context $Context -Namespace 'publish' -Name 'completed' -Value $true -Overwrite Replace -LegacyProperty 'publishCompleted'
}
function Get-EngineState {
param(
[Parameter(Mandatory = $true)]
[psobject]$Context,
[Parameter(Mandatory = $true)]
[string]$Name,
[Parameter(Mandatory = $false)]
$Default,
[switch]$Required
)
if ($script:EngineStateAllowlist -notcontains $Name) {
throw "Engine state key '$Name' is not allowlisted."
}
if ($Context.PSObject.Properties.Name -contains $Name) {
$value = $Context.$Name
if (Test-EngineFactValuePresent -Value $value) {
return $value
}
}
if ($Required) {
throw "Required engine state '$Name' is missing."
}
if ($PSBoundParameters.ContainsKey('Default')) {
return $Default
}
return $null
}
Export-ModuleMember -Function `
Resolve-RelativePaths, `
Initialize-EngineFactsBag, `
Set-EngineFact, `
Get-EngineFact, `
Test-EngineFact, `
Set-EngineState, `
Add-EnginePublishCompletion, `
Get-EngineState