Support/Package/Schema/Eigenverft.Manifested.Sandbox.Package.DefinitionReference.ps1
|
<#
Eigenverft.Manifested.Sandbox.Package.DefinitionReference #> function Get-PackageDefinitionPublication { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$DefinitionDocument, [Parameter(Mandatory = $true)] [string]$DefinitionPath ) if (-not $DefinitionDocument.PSObject.Properties['definitionPublication']) { throw "Package definition '$DefinitionPath' is missing required definitionPublication metadata." } $publication = $DefinitionDocument.definitionPublication foreach ($requiredProperty in @('publisherId', 'publisherName', 'definitionRevision', 'publishedAtUtc')) { if (-not $publication.PSObject.Properties[$requiredProperty] -or [string]::IsNullOrWhiteSpace([string]$publication.$requiredProperty)) { throw "Package definition '$DefinitionPath' is missing definitionPublication.$requiredProperty." } } return [pscustomobject]@{ PublisherId = [string]$publication.publisherId PublisherName = [string]$publication.publisherName DefinitionRevision = [int]$publication.definitionRevision PublishedAtUtc = [string]$publication.publishedAtUtc } } function Get-PackageLocalDefinitionPath { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateSet('Candidate', 'Assigned')] [string]$Role, [Parameter(Mandatory = $true)] [string]$LocalRepositoryRoot, [Parameter(Mandatory = $true)] [string]$PublisherId, [AllowNull()] [string]$DefinitionRepositorySegment = $null, [Parameter(Mandatory = $true)] [string]$DefinitionId ) $safePublisherId = ConvertTo-PackageSafePathSegment -Value $PublisherId $safeDefinitionId = ConvertTo-PackageSafePathSegment -Value $DefinitionId if ([string]::Equals($Role, 'Candidate', [System.StringComparison]::OrdinalIgnoreCase)) { if ([string]::IsNullOrWhiteSpace($DefinitionRepositorySegment)) { throw 'DefinitionRepositorySegment (JSON repositoryId) is required when resolving a Candidate definition path.' } $safeRepositorySegment = ConvertTo-PackageSafePathSegment -Value $DefinitionRepositorySegment return [System.IO.Path]::GetFullPath((Join-Path (Join-Path (Join-Path (Join-Path $LocalRepositoryRoot $Role) $safePublisherId) $safeRepositorySegment) ($safeDefinitionId + '.json'))) } return [System.IO.Path]::GetFullPath((Join-Path (Join-Path (Join-Path $LocalRepositoryRoot $Role) $safePublisherId) ($safeDefinitionId + '.json'))) } function Copy-PackageDefinitionToLocalDefinitionStore { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateSet('Candidate', 'Assigned')] [string]$Role, [Parameter(Mandatory = $true)] [string]$SourcePath, [Parameter(Mandatory = $true)] [string]$LocalRepositoryRoot, [Parameter(Mandatory = $true)] [string]$PublisherId, [AllowNull()] [string]$DefinitionRepositorySegment = $null, [Parameter(Mandatory = $true)] [string]$DefinitionId, [Parameter(Mandatory = $true)] [int]$DefinitionRevision ) $targetPath = Get-PackageLocalDefinitionPath -Role $Role -LocalRepositoryRoot $LocalRepositoryRoot -PublisherId $PublisherId -DefinitionRepositorySegment $DefinitionRepositorySegment -DefinitionId $DefinitionId $targetDirectory = Split-Path -Parent $targetPath $null = New-Item -ItemType Directory -Path $targetDirectory -Force $sourceHash = Get-PackageFileSha256 -Path $SourcePath $targetExists = Test-Path -LiteralPath $targetPath -PathType Leaf if ($targetExists) { $targetHash = Get-PackageFileSha256 -Path $targetPath if ([string]::Equals($sourceHash, $targetHash, [System.StringComparison]::OrdinalIgnoreCase)) { return [pscustomobject]@{ Path = $targetPath Hash = $targetHash Status = 'Reused' RevisionReuse = $false } } try { $existingInfo = Read-PackageJsonDocument -Path $targetPath $existingPublication = Get-PackageDefinitionPublication -DefinitionDocument $existingInfo.Document -DefinitionPath $targetPath if ($existingPublication.DefinitionRevision -eq $DefinitionRevision) { Write-PackageExecutionMessage -Level 'WRN' -Message ("[WARN] Package definition publisher '{0}' reused definitionRevision '{1}' for definition '{2}' with different content; updating local {3} materialized copy." -f $PublisherId, $DefinitionRevision, $DefinitionId, $Role) } } catch { Write-PackageExecutionMessage -Level 'WRN' -Message ("[WARN] Existing local {0} definition copy '{1}' could not be inspected before replacement: {2}" -f $Role, $targetPath, $_.Exception.Message) } } Copy-FileToPath -SourcePath $SourcePath -TargetPath $targetPath -Overwrite | Out-Null return [pscustomobject]@{ Path = $targetPath Hash = Get-PackageFileSha256 -Path $targetPath Status = if ($targetExists) { 'Updated' } else { 'Copied' } RevisionReuse = $targetExists } } function Get-PackageDefinitionJsonPathsUnderDirectory { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$DirectoryPath ) if (-not (Test-Path -LiteralPath $DirectoryPath -PathType Container)) { return @() } return @(Get-ChildItem -LiteralPath $DirectoryPath -Filter '*.json' -File -Recurse | Select-Object -ExpandProperty FullName) } function Select-PackageDefinitionCandidatesFromEndpointScanRoot { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$EndpointName, [Parameter(Mandatory = $true)] [psobject]$EndpointSource, [Parameter(Mandatory = $true)] [string]$ScanRootPath, [Parameter(Mandatory = $true)] [string]$DefinitionId ) $candidates = New-Object System.Collections.Generic.List[object] foreach ($definitionPath in @(Get-PackageDefinitionJsonPathsUnderDirectory -DirectoryPath $ScanRootPath)) { try { $definitionInfo = Read-PackageJsonDocument -Path $definitionPath $definition = $definitionInfo.Document $docDefinitionId = if ($definition.PSObject.Properties['definitionId'] -and -not [string]::IsNullOrWhiteSpace([string]$definition.definitionId)) { [string]$definition.definitionId } elseif ($definition.PSObject.Properties['id']) { [string]$definition.id } else { $null } if ([string]::IsNullOrWhiteSpace($docDefinitionId) -or -not [string]::Equals($docDefinitionId, $DefinitionId, [System.StringComparison]::OrdinalIgnoreCase)) { continue } $definitionDataRepositoryId = if ($definition.PSObject.Properties['repositoryId'] -and -not [string]::IsNullOrWhiteSpace([string]$definition.repositoryId)) { [string]$definition.repositoryId } else { [string](Get-PackageDefaultRepositoryId) } $publication = Get-PackageDefinitionPublication -DefinitionDocument $definition -DefinitionPath $definitionPath $candidates.Add([pscustomobject]@{ EndpointName = $EndpointName RepositorySourceId = $EndpointName RepositoryId = $EndpointName DefinitionDataRepositoryId = $definitionDataRepositoryId EndpointSourceKind = [string]$EndpointSource.kind DefinitionScanRootPath = $ScanRootPath DefinitionId = $docDefinitionId DefinitionPath = [System.IO.Path]::GetFullPath($definitionPath) PublisherId = [string]$publication.PublisherId PublisherName = [string]$publication.PublisherName DefinitionRevision = [int]$publication.DefinitionRevision PublishedAtUtc = [string]$publication.PublishedAtUtc SourceHash = Get-PackageFileSha256 -Path $definitionPath }) | Out-Null } catch { Write-PackageExecutionMessage -Level 'WRN' -Message ("[WARN] Skipped package definition candidate '{0}' from endpoint '{1}': {2}" -f $definitionPath, $EndpointName, $_.Exception.Message) } } return @($candidates.ToArray()) } function Get-PackageEnabledTrustedEndpointSources { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$EndpointInventoryDocument ) $sources = @( foreach ($source in @(Get-PackageEndpointSourceEntries -Document $EndpointInventoryDocument)) { if (-not [bool]$source.enabled) { continue } if (-not [bool]$source.trusted) { continue } $endpointName = [string]$source.endpointName [pscustomobject]@{ EndpointName = $endpointName RepositorySourceId = $endpointName RepositoryId = $endpointName Source = $source SearchOrder = if ($source.PSObject.Properties['searchOrder']) { [int]$source.searchOrder } else { 1000 } } } ) return @($sources | Sort-Object -Property SearchOrder, EndpointName) } function Sync-PackageRepositoryCandidateDefinitions { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [object[]]$SourceRows, [AllowNull()] [string]$ApplicationRootDirectory = $null, [Parameter(Mandatory = $true)] [string]$LocalRepositoryRoot ) $materializedCount = 0 foreach ($sourceRow in @($SourceRows)) { $endpointName = [string]$sourceRow.EndpointName try { $scanRootPath = Resolve-PackageEndpointRootPath -EndpointName $endpointName -Source $sourceRow.Source -ApplicationRootDirectory $ApplicationRootDirectory foreach ($definitionPath in @(Get-PackageDefinitionJsonPathsUnderDirectory -DirectoryPath $scanRootPath)) { try { $definitionInfo = Read-PackageJsonDocument -Path $definitionPath $doc = $definitionInfo.Document $docDefinitionId = if ($doc.PSObject.Properties['definitionId'] -and -not [string]::IsNullOrWhiteSpace([string]$doc.definitionId)) { [string]$doc.definitionId } elseif ($doc.PSObject.Properties['id']) { [string]$doc.id } else { continue } if ([string]::IsNullOrWhiteSpace($docDefinitionId)) { continue } $definitionRepositorySegment = if ($doc.PSObject.Properties['repositoryId'] -and -not [string]::IsNullOrWhiteSpace([string]$doc.repositoryId)) { [string]$doc.repositoryId } else { [string](Get-PackageDefaultRepositoryId) } $publication = Get-PackageDefinitionPublication -DefinitionDocument $doc -DefinitionPath $definitionPath Copy-PackageDefinitionToLocalDefinitionStore -Role 'Candidate' -SourcePath $definitionPath -LocalRepositoryRoot $LocalRepositoryRoot -PublisherId ([string]$publication.PublisherId) -DefinitionRepositorySegment $definitionRepositorySegment -DefinitionId $docDefinitionId -DefinitionRevision ([int]$publication.DefinitionRevision) | Out-Null $materializedCount++ } catch { Write-PackageExecutionMessage -Level 'WRN' -Message ("[WARN] Skipped repository-focused Candidate materialization for '{0}' from endpoint '{1}': {2}" -f $definitionPath, $endpointName, $_.Exception.Message) } } } catch { Write-PackageExecutionMessage -Level 'WRN' -Message ("[WARN] Skipped repository-focused Candidate materialization for endpoint '{0}': {1}" -f $endpointName, $_.Exception.Message) } } return $materializedCount } function Resolve-PackageDefinitionReference { <# .SYNOPSIS Resolves a Package definition identity to a local materialized definition path. .DESCRIPTION PackageEndpointInventory.json lists scan endpoints. All enabled trusted endpoints are searched in searchOrder. Matching uses JSON definitionId. Optional RepositoryId filters to definitions whose JSON repositoryId equals that value (data-level, not an endpoint row key). The winning live definition (highest definitionRevision, then searchOrder, endpointName, publisherId, path) is copied under PkgRepos using publisher and JSON repositoryId path segments. #> [CmdletBinding()] param( [AllowNull()] [string]$RepositoryId = $null, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$DefinitionId, [AllowNull()] [string]$ApplicationRootDirectory = $null, [AllowNull()] [string]$LocalRepositoryRoot = $null, [ValidateSet('packageFocused', 'repositoryFocused')] [string]$RepositoryMaterializationMode = 'packageFocused' ) $endpointInventoryInfo = Get-PackageEndpointInventoryInfo $candidateRows = New-Object System.Collections.Generic.List[object] $sourceRows = @(Get-PackageEnabledTrustedEndpointSources -EndpointInventoryDocument $endpointInventoryInfo.Document) $resolvedLocalRepositoryRoot = if ([string]::IsNullOrWhiteSpace($LocalRepositoryRoot)) { Get-PackageDefaultLocalRepositoryRoot } else { [string]$LocalRepositoryRoot } if ([string]::Equals($RepositoryMaterializationMode, 'repositoryFocused', [System.StringComparison]::OrdinalIgnoreCase)) { $count = Sync-PackageRepositoryCandidateDefinitions -SourceRows $sourceRows -ApplicationRootDirectory $ApplicationRootDirectory -LocalRepositoryRoot $resolvedLocalRepositoryRoot Write-PackageExecutionMessage -Message ("[STATE] Repository-focused definition materialization refreshed {0} Candidate definition file(s)." -f $count) } foreach ($sourceRow in @($sourceRows)) { $endpointName = [string]$sourceRow.EndpointName $scanRootPath = Resolve-PackageEndpointRootPath -EndpointName $endpointName -Source $sourceRow.Source -ApplicationRootDirectory $ApplicationRootDirectory foreach ($candidate in @(Select-PackageDefinitionCandidatesFromEndpointScanRoot -EndpointName $endpointName -EndpointSource $sourceRow.Source -ScanRootPath $scanRootPath -DefinitionId $DefinitionId)) { $candidate | Add-Member -MemberType NoteProperty -Name SearchOrder -Value ([int]$sourceRow.SearchOrder) -Force $candidateRows.Add($candidate) | Out-Null } } $candidates = @($candidateRows.ToArray()) if (-not [string]::IsNullOrWhiteSpace($RepositoryId)) { $candidates = @($candidates | Where-Object { [string]::Equals([string]$_.DefinitionDataRepositoryId, [string]$RepositoryId, [System.StringComparison]::OrdinalIgnoreCase) }) } if ($candidates.Count -eq 0) { $narrow = if ([string]::IsNullOrWhiteSpace($RepositoryId)) { '' } else { " with JSON repositoryId filter '$RepositoryId'" } throw "Package definition '$DefinitionId' was not found in enabled trusted endpoints$narrow." } $bestRevision = (@($candidates) | Measure-Object -Property DefinitionRevision -Maximum).Maximum $bestRevisionCandidates = @($candidates | Where-Object { [int]$_.DefinitionRevision -eq [int]$bestRevision }) $bestHashes = @($bestRevisionCandidates | Select-Object -ExpandProperty SourceHash -Unique) if ($bestHashes.Count -gt 1) { $locations = (@($bestRevisionCandidates) | ForEach-Object { "'$($_.EndpointName):$($_.DefinitionPath) hash=$($_.SourceHash)'" }) -join ', ' throw "Package definition '$DefinitionId' publisher '$($bestRevisionCandidates[0].PublisherId)' reused definitionRevision '$bestRevision' with different content across endpoints. Matching candidates: $locations. Use -RepositoryId to narrow by JSON repositoryId or publish a higher revision." } $selected = @($bestRevisionCandidates | Sort-Object -Property SearchOrder, EndpointName, PublisherId, DefinitionPath | Select-Object -First 1)[0] $selectedSourceRow = @($sourceRows | Where-Object { [string]::Equals([string]$_.EndpointName, [string]$selected.EndpointName, [System.StringComparison]::OrdinalIgnoreCase) } | Select-Object -First 1)[0] $candidateCopy = Copy-PackageDefinitionToLocalDefinitionStore -Role 'Candidate' -SourcePath $selected.DefinitionPath -LocalRepositoryRoot $resolvedLocalRepositoryRoot -PublisherId $selected.PublisherId -DefinitionRepositorySegment $selected.DefinitionDataRepositoryId -DefinitionId $selected.DefinitionId -DefinitionRevision $selected.DefinitionRevision return [pscustomobject]@{ EndpointName = [string]$selected.EndpointName RepositorySourceId = [string]$selected.EndpointName RepositoryId = [string]$selected.EndpointName DefinitionDataRepositoryId = [string]$selected.DefinitionDataRepositoryId DefinitionId = [string]$selected.DefinitionId DefinitionPath = [System.IO.Path]::GetFullPath($candidateCopy.Path) SourceKind = [string]$selected.EndpointSourceKind SourcePath = [string]$selected.DefinitionPath SourceDefinitionScanRootPath = [string]$selected.DefinitionScanRootPath SourceHash = [string]$selected.SourceHash CandidatePath = [System.IO.Path]::GetFullPath($candidateCopy.Path) CandidateHash = [string]$candidateCopy.Hash SnapshotPath = $null SnapshotHash = $null ResolvedAtUtc = [DateTime]::UtcNow.ToString('o') SnapshotFallback = $false EndpointInventoryPath = $endpointInventoryInfo.Path Trusted = $true TrustMode = if ($selectedSourceRow) { [string]$selectedSourceRow.Source.trustMode } else { $null } PublisherId = [string]$selected.PublisherId PublisherName = [string]$selected.PublisherName DefinitionRevision = [int]$selected.DefinitionRevision PublishedAtUtc = [string]$selected.PublishedAtUtc MaterializationStatus = [string]$candidateCopy.Status RepositoryMaterializationMode = [string]$RepositoryMaterializationMode } } |