Private/Migration/Invoke-CgrNewDestinationSnapshot.ps1
|
function Invoke-CgrNewDestinationSnapshot { <# .SYNOPSIS Orchestrates verified Snapshot publication to a newly created destination. .DESCRIPTION Publishes the approved clean Snapshot, reloads and verifies destination content, optionally restores the exact GitHub Releases approved during planning against generated Snapshot release tags, then restores supported settings and protection. Configuration restoration is deliberately blocked after content-verification failure. .NOTES The destination repository exists before entry. Copy-GitHubRepository owns the public ShouldProcess decision; this layer owns mutation ordering, provenance, verification gating, and recovery reporting after mutation begins. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'The public Copy-GitHubRepository command performs ShouldProcess before calling this orchestration boundary.' )] [CmdletBinding()] param( [Parameter(Mandatory)] [psobject] $Plan, [Parameter(Mandatory)] [psobject] $SourceRepository, [Parameter(Mandatory)] [psobject] $DestinationRepository, [ValidateNotNullOrEmpty()] [string] $HostName = 'github.com', [string] $ReportPath ) $completedSteps = [System.Collections.Generic.List[object]]::new() $completedSteps.Add([pscustomobject] @{ Order = 1; Name = 'CreateDestinationRepository'; MutatedGitHub = $true; Verified = $true }) $failureStage = 'CopySnapshot' $snapshot = $null $snapshotRootCommitSha = $null $verifiedDestination = $null $snapshotGeneratedCommitProgress = [System.Collections.Generic.List[object]]::new() $sourceState = Get-CgrObjectProperty -InputObject $Plan -Name 'SourceState' $releaseCheckpointPlan = Get-CgrObjectProperty -InputObject $Plan -Name 'ReleaseCheckpointPlan' $includeReleases = [bool] (Get-CgrObjectProperty -InputObject $Plan -Name 'IncludeReleases') $releaseSelection = Get-CgrObjectProperty -InputObject $Plan -Name 'ReleaseSelection' $releases = [pscustomobject] @{ PSTypeName = 'CopyGitHubRepo.ReleaseMigrationResult' SourceRepository = $SourceRepository.FullName DestinationRepository = $DestinationRepository.FullName ApprovedReleaseCount = 0 DestinationReleaseCount = 0 Releases = @() Unsupported = @() IsSuccessful = $true Status = 'NotRequested' } try { $snapshot = @(Copy-CgrRepositorySnapshot -SourceRepository $SourceRepository -DestinationRepository $DestinationRepository -BranchName $Plan.SourceDefaultBranch -CommitMessage $Plan.CommitMessage -ApprovedSourceState $sourceState -ReleaseCheckpointPlan $releaseCheckpointPlan -RecoveryGeneratedCommits $snapshotGeneratedCommitProgress)[-1] $snapshotRootCommitSha = Get-CgrObjectProperty -InputObject $snapshot -Name 'RootCommitSha' if ($null -eq $snapshotRootCommitSha) { $snapshotRootCommitSha = Get-CgrObjectProperty -InputObject $snapshot -Name 'CommitSha' } $completedSteps.Add([pscustomobject] @{ Order = 2; Name = 'CopySnapshot'; MutatedGitHub = $true; Verified = $snapshot.Verified }) $failureStage = 'ReloadDestination' $verifiedDestination = Get-CgrRepository -Repository $DestinationRepository.FullName -HostName $HostName $failureStage = 'VerifySnapshot' $verification = Invoke-CgrActivityStage -Name 'VerifyDestinationContent' -Message 'Verify destination content' -Action { if ($releaseCheckpointPlan) { Invoke-CgrApprovedSnapshotReleaseVerification ` -SourceRepository $SourceRepository ` -DestinationRepository $verifiedDestination ` -ReleaseCheckpointPlan $releaseCheckpointPlan } else { Invoke-CgrRepositorySnapshotVerification ` -SourceRepository $SourceRepository ` -DestinationRepository $verifiedDestination ` -ApprovedSourceState $sourceState } } $completedSteps.Add([pscustomobject] @{ Order = 3; Name = 'VerifySnapshot'; MutatedGitHub = $false; Verified = $verification.IsSuccessful }) if ($includeReleases) { $failureStage = 'RestoreGitHubReleases' if (-not $verification.IsSuccessful) { $releases = [pscustomobject] @{ PSTypeName = 'CopyGitHubRepo.ReleaseMigrationResult' SourceRepository = $SourceRepository.FullName DestinationRepository = $verifiedDestination.FullName ApprovedReleaseCount = @($releaseSelection.Releases).Count DestinationReleaseCount = 0 Releases = @() Unsupported = @() IsSuccessful = $false Status = 'SnapshotVerificationFailed' } } else { $releases = Invoke-CgrActivityStage -Name 'RestoreGitHubReleases' -Message 'Restore approved GitHub Releases and assets' -Action { Copy-CgrApprovedGitHubRelease ` -SourceRepository $SourceRepository ` -DestinationRepository $verifiedDestination ` -ApprovedSelection $releaseSelection ` -DestinationTagTargets @($snapshot.ReleaseTags) ` -HostName $HostName } $releases | Add-Member -NotePropertyName Status -NotePropertyValue 'Restored' -Force } $completedSteps.Add([pscustomobject] @{ Order = $completedSteps.Count + 1 Name = 'RestoreGitHubReleases' MutatedGitHub = [bool] $verification.IsSuccessful Verified = $releases.IsSuccessful }) } $configuration = Invoke-CgrPostVerificationConfigurationRestore ` -Plan $Plan ` -SourceRepository $SourceRepository ` -DestinationRepository $verifiedDestination ` -Verification $verification ` -VerificationFailureReason 'SnapshotVerificationFailed' ` -CompletedSteps $completedSteps ` -FailureStage ([ref] $failureStage) ` -HostName $HostName $settings = $configuration.Settings $protection = $configuration.Protection $snapshotSourceCommit = if ($sourceState) { Get-CgrObjectProperty -InputObject $sourceState -Name 'CommitSha' } else { Get-CgrObjectProperty -InputObject $snapshot -Name 'SourceCommitSha' } $snapshotTree = if ($sourceState) { Get-CgrObjectProperty -InputObject $sourceState -Name 'TreeSha' } else { Get-CgrObjectProperty -InputObject $snapshot -Name 'TreeSha' } if ($null -eq $snapshotTree) { $snapshotTree = Get-CgrObjectProperty -InputObject $verification -Name 'SourceTree' } $destinationTree = Get-CgrObjectProperty -InputObject $verification -Name 'DestinationTree' if ($null -eq $destinationTree) { $destinationTree = Get-CgrObjectProperty -InputObject $verification -Name 'DestinationHeadTreeSha' } if ($null -eq $destinationTree) { $destinationTree = $snapshotTree } $releasePreservation = Get-CgrSnapshotReleasePreservationEvidence ` -Plan $Plan ` -DestinationRepository $verifiedDestination ` -SnapshotCopyResult $snapshot ` -ReleaseRestoreResult $releases ` -GeneratedCommitProgress @($snapshotGeneratedCommitProgress) ` -HostName $HostName $provenance = [pscustomobject] @{ PSTypeName = 'CopyGitHubRepo.SnapshotPublicationProvenance' RecordedAtUtc = [DateTimeOffset]::UtcNow.ToString('o') ContentMode = 'Snapshot' SourceRepository = $Plan.SourceRepository SourceRepositoryId = if ($sourceState) { Get-CgrObjectProperty -InputObject $sourceState -Name 'RepositoryId' } else { Get-CgrObjectProperty -InputObject $SourceRepository -Name 'Id' } SourceRepositoryNodeId = if ($sourceState) { Get-CgrObjectProperty -InputObject $sourceState -Name 'RepositoryNodeId' } else { Get-CgrObjectProperty -InputObject $SourceRepository -Name 'NodeId' } SourceDefaultBranch = $Plan.SourceDefaultBranch SourceCommitSha = $snapshotSourceCommit SourceTreeSha = $snapshotTree PlannedSourceState = $sourceState CopiedSourceCommitSha = Get-CgrObjectProperty -InputObject $snapshot -Name 'SourceCommitSha' CopiedSourceTreeSha = Get-CgrObjectProperty -InputObject $snapshot -Name 'TreeSha' DestinationRepository = $verifiedDestination.FullName DestinationRepositoryId = Get-CgrObjectProperty -InputObject $verifiedDestination -Name 'Id' DestinationRepositoryNodeId = Get-CgrObjectProperty -InputObject $verifiedDestination -Name 'NodeId' DestinationRootCommitSha = $snapshotRootCommitSha DestinationTreeSha = $destinationTree VerificationSuccessful = [bool] $verification.IsSuccessful ReleasePreservation = $releasePreservation } $releaseSuccessful = -not $includeReleases -or $releases.IsSuccessful $executionResult = [pscustomobject] @{ PSTypeName = 'CopyGitHubRepo.MigrationExecutionResult'; SchemaVersion = 1 Status = if (-not $verification.IsSuccessful) { 'SnapshotVerificationFailed' } elseif (-not $releaseSuccessful) { 'ReleaseRestoreFailed' } elseif ($Plan.SkipSettings) { 'SnapshotVerifiedSettingsSkipped' } elseif ($settings.IsSuccessful -and $protection.IsSuccessful) { 'CompletedWithSupportedSettings' } elseif (-not $settings.IsSuccessful) { 'SettingsRestoreFailed' } else { 'ProtectionRestoreFailed' } SourceRepository = $Plan.SourceRepository; ApprovedSourceState = $sourceState; DestinationRepository = $DestinationRepository.FullName SourceVisibility = $Plan.SourceVisibility; DestinationVisibility = $Plan.DestinationVisibility; DestinationHtmlUrl = $DestinationRepository.HtmlUrl DestinationBranch = Get-CgrObjectProperty -InputObject $snapshot -Name 'BranchName'; SnapshotCommitSha = Get-CgrObjectProperty -InputObject $snapshot -Name 'CommitSha'; SourceTreeSha = $snapshotTree Provenance = $provenance; IsVerified = $verification.IsSuccessful -and $releaseSuccessful; Verification = $verification; Releases = $releases; Settings = $settings; Protection = $protection; Plan = $Plan; CompletedSteps = @($completedSteps) StoppedBeforeSettingsRestore = -not $verification.IsSuccessful ReleasesRestored = [bool] ($includeReleases -and $releases.IsSuccessful) SettingsRestored = $configuration.SettingsRestored ProtectionRestored = $configuration.ProtectionRestored } if ($ReportPath) { $failureStage = 'WriteCompletionReport'; Write-CgrMigrationExecutionReport -Result $executionResult -Path $ReportPath | Out-Null } return $executionResult } catch { $recoveryReportPath = $null try { $recoveryDestination = if ($verifiedDestination) { $verifiedDestination } else { $DestinationRepository } $releasePreservation = Get-CgrSnapshotReleasePreservationEvidence ` -Plan $Plan ` -DestinationRepository $recoveryDestination ` -SnapshotCopyResult $snapshot ` -ReleaseRestoreResult $releases ` -GeneratedCommitProgress @($snapshotGeneratedCommitProgress) ` -HostName $HostName $recoveryProvenance = [pscustomobject] @{ ContentMode = 'Snapshot'; SourceRepository = $Plan.SourceRepository SourceRepositoryId = if ($sourceState) { Get-CgrObjectProperty -InputObject $sourceState -Name 'RepositoryId' } else { Get-CgrObjectProperty -InputObject $SourceRepository -Name 'Id' } SourceRepositoryNodeId = if ($sourceState) { Get-CgrObjectProperty -InputObject $sourceState -Name 'RepositoryNodeId' } else { Get-CgrObjectProperty -InputObject $SourceRepository -Name 'NodeId' } SourceDefaultBranch = $Plan.SourceDefaultBranch SourceCommitSha = if ($sourceState) { Get-CgrObjectProperty -InputObject $sourceState -Name 'CommitSha' } elseif ($snapshot) { Get-CgrObjectProperty -InputObject $snapshot -Name 'SourceCommitSha' } else { $null } SourceTreeSha = if ($sourceState) { Get-CgrObjectProperty -InputObject $sourceState -Name 'TreeSha' } elseif ($snapshot) { Get-CgrObjectProperty -InputObject $snapshot -Name 'TreeSha' } else { $null } PlannedSourceState = $sourceState DestinationRepository = $DestinationRepository.FullName DestinationRepositoryId = if ($verifiedDestination) { Get-CgrObjectProperty -InputObject $verifiedDestination -Name 'Id' } else { Get-CgrObjectProperty -InputObject $DestinationRepository -Name 'Id' } DestinationRepositoryNodeId = if ($verifiedDestination) { Get-CgrObjectProperty -InputObject $verifiedDestination -Name 'NodeId' } else { Get-CgrObjectProperty -InputObject $DestinationRepository -Name 'NodeId' } DestinationRootCommitSha = $snapshotRootCommitSha DestinationTreeSha = if ($snapshot) { Get-CgrObjectProperty -InputObject $snapshot -Name 'TreeSha' } else { $null } ReleasePreservation = $releasePreservation } $recoveryReportPath = Write-CgrMigrationRecoveryReport -Plan $Plan -DestinationRepository $DestinationRepository -FailureStage $failureStage -ErrorRecord $_ -CompletedSteps @($completedSteps) -Provenance $recoveryProvenance -PreferredReportPath $ReportPath } catch { Write-Warning "Migration failed after destination creation, and the recovery report could not be written. Recovery reporting error: $($_.Exception.Message)" } if ($recoveryReportPath) { Write-Warning "Migration failed after destination creation. Recovery report: $recoveryReportPath" } throw } } |