Commands/Depot/Eigenverft.Manifested.Package.Cmd.PackageDepot.ps1
|
<#
Public depot-management command surface. #> function Get-PackageDepot { [CmdletBinding()] param( [string]$DepotId ) $depots = @(Get-PackageDepotSummaries) if ([string]::IsNullOrWhiteSpace($DepotId)) { return $depots } $match = @($depots | Where-Object { [string]::Equals([string]$_.DepotId, $DepotId, [System.StringComparison]::OrdinalIgnoreCase) }) if ($match.Count -eq 0) { throw "Package depot '$DepotId' was not found in '$((Get-PackageDepotInventoryPath))'." } return $match } function Invoke-PackageDepotMaterialize { <# .SYNOPSIS Materializes every already trusted package available for the current platform. .DESCRIPTION Discovers package definitions whose effective catalog status is signedTrusted, deduplicates their publisher/definition identities, and materializes each package plus its dependencies into configured writable mirrors. A package is materialized when its complete file set is durable in at least one readable depot. Incomplete secondary mirrors remain visible in the result without discarding that success. A failed package is reported and the next package is attempted unless FailFast is selected. By default, the command never prompts for new catalog trust, imports signing keys, accepts unsigned definitions, installs packages, changes depot configuration, or removes files. AcceptUnknownSigningKey is an explicit opt-in for signed root definitions whose signing key is not yet trusted; unsigned and blocked root definitions remain excluded, while dependency trust follows the existing package policy. .PARAMETER AllTrusted Legacy compatibility switch. The already-trusted current-platform catalog is now the default scope. .PARAMETER AcceptUnknownSigningKey Also includes signed root definitions whose signing key is not yet trusted and explicitly accepts that key through the existing package trust workflow. Unsigned and blocked root definitions remain excluded; dependency trust follows the existing package policy. .PARAMETER ExcludeDefinitionId Omits matching definition ids from this run. .PARAMETER FailFast Stops after the first package that does not materialize successfully. #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] param( [Parameter(DontShow = $true)] [switch]$AllTrusted, [switch]$AcceptUnknownSigningKey, [AllowNull()] [string]$PublisherId = $null, [AllowNull()] [string[]]$Tag = @(), [AllowNull()] [string[]]$ExcludeDefinitionId = @(), [switch]$FailFast ) if ($AllTrusted.IsPresent) { Write-Verbose '-AllTrusted is retained for compatibility; trusted current-platform definitions are now selected by default.' } $searchParameters = @{ CurrentPlatformOnly = $true IncludeIneligible = $true } if (-not [string]::IsNullOrWhiteSpace($PublisherId)) { $searchParameters.PublisherId = $PublisherId } $normalizedTags = @($Tag | ForEach-Object { ([string]$_).Trim() } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) if ($normalizedTags.Count -gt 0) { $searchParameters.Tag = $normalizedTags } $excluded = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase) foreach ($definitionId in @($ExcludeDefinitionId)) { $normalizedDefinitionId = ([string]$definitionId).Trim() if (-not [string]::IsNullOrWhiteSpace($normalizedDefinitionId)) { $excluded.Add($normalizedDefinitionId) | Out-Null } } $acceptedTrustStatuses = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase) $acceptedTrustStatuses.Add('signedTrusted') | Out-Null if ($AcceptUnknownSigningKey.IsPresent) { $acceptedTrustStatuses.Add('signedUnknownKeyPrompt') | Out-Null $acceptedTrustStatuses.Add('signedUnknownKeyAutoTrust') | Out-Null } $trustedCandidates = @( Search-Package @searchParameters | Where-Object { $acceptedTrustStatuses.Contains([string]$_.CatalogTrustStatus) -and -not $excluded.Contains([string]$_.DefinitionId) } ) $selected = @( $trustedCandidates | Sort-Object -Property PublisherId, DefinitionId, EndpointSearchOrder, @{ Expression = 'DefinitionRevision'; Descending = $true } | Group-Object -Property { Get-PackageDependencyReferenceKey -PublisherId ([string]$_.PublisherId) -DefinitionId ([string]$_.DefinitionId) } | ForEach-Object { $_.Group | Select-Object -First 1 } | Sort-Object -Property PublisherId, DefinitionId ) if ($selected.Count -eq 0) { Write-Verbose 'No package definitions are available for the requested current-platform trust scope.' return } $trustParameters = if ($AcceptUnknownSigningKey.IsPresent) { @{ AcceptUnknownSigningKey = $true } } else { @{ RequireAlreadyTrusted = $true } } $targetScope = if ($AcceptUnknownSigningKey.IsPresent) { 'trusted or explicitly accepted unknown-key current-platform package definition(s)' } else { 'already trusted current-platform package definition(s)' } $target = '{0} {1}' -f $selected.Count, $targetScope if (-not $PSCmdlet.ShouldProcess($target, 'Materialize complete artifact sets into configured package depots')) { foreach ($package in $selected) { $assignmentPlan = New-PackageAssignmentPlanCore -PublisherId ([string]$package.PublisherId) -DefinitionId ([string]$package.DefinitionId) -Purpose Inspection -MaterializeOnly @trustParameters [pscustomobject]@{ PSTypeName = 'Eigenverft.Manifested.Package.DepotMaterializeResult' PublisherId = [string]$package.PublisherId DefinitionId = [string]$package.DefinitionId Version = [string]$package.Version Status = if ($assignmentPlan.Accepted) { 'Planned' } else { 'Blocked' } AssignmentPlan = $assignmentPlan BlockerSummary = @($assignmentPlan.Blockers) PackageResult = @() ErrorMessage = $null } } return } foreach ($package in $selected) { $packageResults = @() $errorMessage = $null try { $packageResults = @(Invoke-Package -PublisherId ([string]$package.PublisherId) -DefinitionId ([string]$package.DefinitionId) -MaterializeOnly @trustParameters) } catch { $errorMessage = $_.Exception.Message } $failedResults = @($packageResults | Where-Object { -not ([string]$_.Status -in @('Ready', 'Materialized')) }) $status = if (-not [string]::IsNullOrWhiteSpace($errorMessage) -or $failedResults.Count -gt 0 -or $packageResults.Count -eq 0) { 'Failed' } else { 'Materialized' } $materializeResult = [pscustomobject]@{ PSTypeName = 'Eigenverft.Manifested.Package.DepotMaterializeResult' PublisherId = [string]$package.PublisherId DefinitionId = [string]$package.DefinitionId Version = [string]$package.Version Status = $status AssignmentPlan = $null BlockerSummary = @() PackageResult = @($packageResults) ErrorMessage = $errorMessage } $materializeResult if ($FailFast.IsPresent -and [string]::Equals($status, 'Failed', [System.StringComparison]::OrdinalIgnoreCase)) { break } } } function Add-PackageDepot { [CmdletBinding(SupportsShouldProcess = $true)] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$DepotId, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$BasePath, [int]$SearchOrder = -1, [string]$After, [string[]]$SiteCode = @(), [switch]$Disabled, [switch]$Writable, [switch]$MirrorTarget, [switch]$EnsureExists ) Assert-PackageDepotId -DepotId $DepotId $documentInfo = Get-PackageDepotInventoryEditInfo if (Get-PackageDepotSourceProperty -Document $documentInfo.Document -DepotId $DepotId) { throw "Package depot '$DepotId' already exists in '$($documentInfo.Path)'. Use Set-PackageDepot to modify it." } $resolvedSearchOrder = if ($SearchOrder -ge 0) { $SearchOrder } elseif (-not [string]::IsNullOrWhiteSpace($After)) { Get-PackageDepotSearchOrderAfter -Document $documentInfo.Document -AfterDepotId $After } else { Get-PackageNextDepotSearchOrder -Document $documentInfo.Document } $source = New-PackageFilesystemDepotSource -BasePath $BasePath -SearchOrder $resolvedSearchOrder -Enabled (-not $Disabled.IsPresent) -SiteCodes $SiteCode -Writable $Writable.IsPresent -MirrorTarget $MirrorTarget.IsPresent -EnsureExists $EnsureExists.IsPresent if ($PSCmdlet.ShouldProcess($documentInfo.Path, "Add package depot '$DepotId'")) { $documentInfo.Document.acquisitionEnvironment.environmentSources | Add-Member -MemberType NoteProperty -Name $DepotId -Value $source -Force Save-PackageDepotInventoryDocument -DocumentInfo $documentInfo } $after = Get-PackageDepot -DepotId $DepotId $notes = New-Object System.Collections.Generic.List[string] if ($Disabled.IsPresent) { $notes.Add("Depot '$DepotId' was added disabled; package acquisition will not use it until enabled.") | Out-Null } if (-not $Writable.IsPresent) { $notes.Add("Depot '$DepotId' is read-only from Package perspective by default; downloads will not be mirrored there.") | Out-Null } return New-PackageDepotCommandResult -Action 'Add' -DepotId $DepotId -InventoryPath $documentInfo.Path -Before $null -After $after -Status 'Added' -Notes @($notes.ToArray()) } function Add-TeamPackageDepot { [CmdletBinding(SupportsShouldProcess = $true)] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$BasePath, [ValidateNotNullOrEmpty()] [string]$DepotId = 'teamPackageDepot', [int]$SearchOrder = -1, [string]$After, [string[]]$SiteCode = @(), [switch]$Disabled ) $parameters = @{ DepotId = $DepotId BasePath = $BasePath SiteCode = $SiteCode Writable = $true MirrorTarget = $true EnsureExists = $true } if ($SearchOrder -ge 0) { $parameters.SearchOrder = $SearchOrder } elseif (-not [string]::IsNullOrWhiteSpace($After)) { $parameters.After = $After } else { $parameters.SearchOrder = 150 } if ($Disabled.IsPresent) { $parameters.Disabled = $true } if ($PSBoundParameters.ContainsKey('WhatIf')) { $parameters.WhatIf = [bool]$PSBoundParameters.WhatIf } if ($PSBoundParameters.ContainsKey('Confirm')) { $parameters.Confirm = [bool]$PSBoundParameters.Confirm } return Add-PackageDepot @parameters } function Set-PackageDepot { [CmdletBinding(SupportsShouldProcess = $true)] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$DepotId, [string]$BasePath, [Nullable[int]]$SearchOrder, [string[]]$SiteCode, [switch]$ClearSiteCode, [switch]$Enable, [switch]$Disable, [switch]$Readable, [switch]$NotReadable, [switch]$Writable, [switch]$ReadOnly, [switch]$MirrorTarget, [switch]$NoMirrorTarget, [switch]$EnsureExists, [switch]$NoEnsureExists ) Assert-PackageDepotId -DepotId $DepotId if ($Enable.IsPresent -and $Disable.IsPresent) { throw 'Use either -Enable or -Disable, not both.' } if ($Readable.IsPresent -and $NotReadable.IsPresent) { throw 'Use either -Readable or -NotReadable, not both.' } if ($Writable.IsPresent -and $ReadOnly.IsPresent) { throw 'Use either -Writable or -ReadOnly, not both.' } if ($MirrorTarget.IsPresent -and $NoMirrorTarget.IsPresent) { throw 'Use either -MirrorTarget or -NoMirrorTarget, not both.' } if ($EnsureExists.IsPresent -and $NoEnsureExists.IsPresent) { throw 'Use either -EnsureExists or -NoEnsureExists, not both.' } if ($ClearSiteCode.IsPresent -and $PSBoundParameters.ContainsKey('SiteCode')) { throw 'Use either -SiteCode or -ClearSiteCode, not both.' } $before = Get-PackageDepot -DepotId $DepotId $documentInfo = Get-PackageDepotInventoryEditInfo $sourceProperty = Get-PackageDepotSourceProperty -Document $documentInfo.Document -DepotId $DepotId if (-not $sourceProperty) { throw "Package depot '$DepotId' was not found in '$($documentInfo.Path)'. Use Add-PackageDepot to create it." } $source = $sourceProperty.Value if (-not [string]::Equals([string]$source.kind, 'filesystem', [System.StringComparison]::OrdinalIgnoreCase)) { throw "Package depot '$DepotId' is kind '$($source.kind)'. Only filesystem depots can be modified by Set-PackageDepot." } if ($PSBoundParameters.ContainsKey('BasePath')) { $source | Add-Member -MemberType NoteProperty -Name 'basePath' -Value $BasePath -Force } if ($PSBoundParameters.ContainsKey('SearchOrder')) { $source | Add-Member -MemberType NoteProperty -Name 'searchOrder' -Value ([int]$SearchOrder.Value) -Force } if ($Enable.IsPresent) { $source | Add-Member -MemberType NoteProperty -Name 'enabled' -Value $true -Force } if ($Disable.IsPresent) { $source | Add-Member -MemberType NoteProperty -Name 'enabled' -Value $false -Force } if ($Readable.IsPresent) { $source | Add-Member -MemberType NoteProperty -Name 'readable' -Value $true -Force } if ($NotReadable.IsPresent) { $source | Add-Member -MemberType NoteProperty -Name 'readable' -Value $false -Force } if ($Writable.IsPresent) { $source | Add-Member -MemberType NoteProperty -Name 'writable' -Value $true -Force } if ($ReadOnly.IsPresent) { $source | Add-Member -MemberType NoteProperty -Name 'writable' -Value $false -Force $source | Add-Member -MemberType NoteProperty -Name 'mirrorTarget' -Value $false -Force $source | Add-Member -MemberType NoteProperty -Name 'ensureExists' -Value $false -Force } if ($MirrorTarget.IsPresent) { $source | Add-Member -MemberType NoteProperty -Name 'mirrorTarget' -Value $true -Force } if ($NoMirrorTarget.IsPresent) { $source | Add-Member -MemberType NoteProperty -Name 'mirrorTarget' -Value $false -Force } if ($EnsureExists.IsPresent) { $source | Add-Member -MemberType NoteProperty -Name 'ensureExists' -Value $true -Force } if ($NoEnsureExists.IsPresent) { $source | Add-Member -MemberType NoteProperty -Name 'ensureExists' -Value $false -Force } if ($PSBoundParameters.ContainsKey('SiteCode')) { $source | Add-Member -MemberType NoteProperty -Name 'siteCodes' -Value @($SiteCode | ForEach-Object { ([string]$_).Trim() } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) -Force } if ($ClearSiteCode.IsPresent -and $source.PSObject.Properties['siteCodes']) { $source.PSObject.Properties.Remove('siteCodes') } if ([bool]$source.mirrorTarget -and -not [bool]$source.writable) { throw "Package depot '$DepotId' cannot use MirrorTarget=true while Writable=false. Add -Writable or remove -MirrorTarget." } if ([bool]$source.ensureExists -and -not [bool]$source.writable) { throw "Package depot '$DepotId' cannot use EnsureExists=true while Writable=false. Add -Writable or remove -EnsureExists." } if ($PSCmdlet.ShouldProcess($documentInfo.Path, "Set package depot '$DepotId'")) { Save-PackageDepotInventoryDocument -DocumentInfo $documentInfo } $after = Get-PackageDepot -DepotId $DepotId $notes = New-Object System.Collections.Generic.List[string] if (-not [bool]$after.Enabled) { $notes.Add("Depot '$DepotId' was updated but remains disabled; package acquisition will not use it until enabled.") | Out-Null } elseif (-not [bool]$after.Effective) { $notes.Add("Depot '$DepotId' is enabled but is not currently effective. Check site codes or config validation notes.") | Out-Null } if ($PSBoundParameters.ContainsKey('BasePath')) { $notes.Add("Depot '$DepotId' base path is now '$($after.BasePath)' and resolves to '$($after.ResolvedBasePath)'.") | Out-Null } return New-PackageDepotCommandResult -Action 'Set' -DepotId $DepotId -InventoryPath $documentInfo.Path -Before $before -After $after -Status 'Updated' -Notes @($notes.ToArray()) } function Remove-PackageDepot { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$DepotId, [switch]$Force ) Assert-PackageDepotId -DepotId $DepotId if ([string]::Equals($DepotId, 'defaultPackageDepot', [System.StringComparison]::OrdinalIgnoreCase) -and -not $Force.IsPresent) { throw "Removing 'defaultPackageDepot' can leave Package without a local durable depot. Re-run with -Force if this is intentional." } $before = Get-PackageDepot -DepotId $DepotId $documentInfo = Get-PackageDepotInventoryEditInfo $sourceProperty = Get-PackageDepotSourceProperty -Document $documentInfo.Document -DepotId $DepotId if (-not $sourceProperty) { throw "Package depot '$DepotId' was not found in '$($documentInfo.Path)'." } if ($PSCmdlet.ShouldProcess($documentInfo.Path, "Remove package depot '$DepotId'")) { $documentInfo.Document.acquisitionEnvironment.environmentSources.PSObject.Properties.Remove($DepotId) Save-PackageDepotInventoryDocument -DocumentInfo $documentInfo } return New-PackageDepotCommandResult -Action 'Remove' -DepotId $DepotId -InventoryPath $documentInfo.Path -Before $before -After $null -Status 'Removed' -Notes @( "Depot '$DepotId' was removed from configuration only. Existing depot files were not deleted." ) } |