Public/Set-TaxEditorKnownGood.ps1
|
# Copyright (c) 2026 Jeffrey Snover. All rights reserved. # Licensed under the MIT License. See LICENSE file in the project root. function Set-TaxEditorKnownGood { <# .SYNOPSIS Tags the current production image as known-good in GHCR. .DESCRIPTION Queries ACA for the active revision's image, then moves the 'known-good' tag in GHCR to that image via the OCI Distribution API. Maintains the exactly-one invariant: only one image carries the known-good tag at any time. Supports -WhatIf/-Confirm. .PARAMETER ResourceGroup Azure resource group name. Default: ai-triad. .PARAMETER AppName Container App name. Default: taxonomy-editor. .PARAMETER Package GitHub package in owner/name format. Default: jpsnover/taxonomy-editor. .EXAMPLE Set-TaxEditorKnownGood .EXAMPLE Set-TaxEditorKnownGood -WhatIf .LINK Show-AITriadHelp .LINK Get-TaxEditorBlob .LINK Get-TaxEditorDataCommit .LINK Restore-TaxEditorBlob .LINK Restore-TaxEditorKnownGood .LINK Sync-TaxEditorData .LINK Undo-TaxEditorDataCommit #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( [Parameter()] [string]$ResourceGroup = 'ai-triad', [Parameter()] [string]$AppName = 'taxonomy-editor', [Parameter()] [string]$Package = 'jpsnover/taxonomy-editor' ) Set-StrictMode -Version Latest $CallerName = 'Set-TaxEditorKnownGood' # ── Get active revision's image tag ────────────────────────────────── $Revisions = @(Get-TaxEditorRevision -ResourceGroup $ResourceGroup -AppName $AppName) $Active = @($Revisions | Where-Object { $_.Active }) if (@($Active).Count -eq 0) { throw (New-ActionableError ` -Goal 'Tag production image as known-good' ` -Problem 'No active revision found — cannot determine production image' ` -Location $CallerName ` -NextSteps @('Run Get-TaxEditorRevision to inspect current state', 'Deploy an image first with Deploy-TaxEditorImage')) } $ImageRef = $Active[0].ImageTag if (-not $ImageRef -or $ImageRef -notmatch ':') { throw (New-ActionableError ` -Goal 'Tag production image as known-good' ` -Problem "Active revision image ref is missing or has no tag: '$ImageRef'" ` -Location $CallerName ` -NextSteps @('The active revision may be deployed by digest — tag it first')) } $CurrentTag = ($ImageRef -split ':')[-1] # ── Find previous known-good (for reporting) ───────────────────────── $PreviousKnownGood = '(none)' try { $Images = @(Get-TaxEditorImage -Last 100 -Package $Package) $KnownGoodImages = @($Images | Where-Object { $_.IsKnownGood }) if (@($KnownGoodImages).Count -gt 0) { $OtherTags = @($KnownGoodImages[0].Tags | Where-Object { $_ -ne 'known-good' }) if (@($OtherTags).Count -gt 0) { $PreviousKnownGood = $OtherTags[0] } else { $PreviousKnownGood = $KnownGoodImages[0].Digest.Substring(0, [Math]::Min(19, $KnownGoodImages[0].Digest.Length)) } } } catch { Write-Verbose "Could not determine previous known-good: $($_.Exception.Message)" } # ── WhatIf / Confirm gate ──────────────────────────────────────────── $WhatIfMsg = "Tag image '${ImageRef}' as known-good" if ($PreviousKnownGood -ne '(none)') { $WhatIfMsg += " (replacing previous: $PreviousKnownGood)" } if (-not $PSCmdlet.ShouldProcess("ghcr.io/$Package", $WhatIfMsg)) { return } # ── Get GHCR auth token ────────────────────────────────────────────── $GhcrToken = Get-GhcrAuthToken -Package $Package -Scope 'pull,push' -CallerName $CallerName # ── Get manifest for current tag ───────────────────────────────────── $AcceptTypes = @( 'application/vnd.oci.image.index.v1+json' 'application/vnd.docker.distribution.manifest.v2+json' 'application/vnd.docker.distribution.manifest.list.v2+json' ) -join ', ' try { $ManifestResp = Invoke-WebRequest ` -Uri "https://ghcr.io/v2/$Package/manifests/$CurrentTag" ` -Headers @{ Authorization = "Bearer $GhcrToken"; Accept = $AcceptTypes } ` -ErrorAction Stop } catch { throw (New-ActionableError ` -Goal "Fetch manifest for tag '$CurrentTag'" ` -Problem "GHCR manifest GET failed: $($_.Exception.Message)" ` -Location $CallerName ` -NextSteps @("Verify image exists: ghcr.io/${Package}:${CurrentTag}", 'Check GITHUB_TOKEN has read:packages scope')) } $ManifestContentType = $ManifestResp.Headers['Content-Type'] if ($ManifestContentType -is [array]) { $ManifestContentType = $ManifestContentType[0] } $ManifestBody = $ManifestResp.Content # ── PUT manifest with known-good tag ───────────────────────────────── try { Invoke-RestMethod ` -Uri "https://ghcr.io/v2/$Package/manifests/known-good" ` -Method PUT ` -Headers @{ Authorization = "Bearer $GhcrToken" } ` -ContentType $ManifestContentType ` -Body $ManifestBody ` -ErrorAction Stop | Out-Null } catch { throw (New-ActionableError ` -Goal "Tag image as 'known-good'" ` -Problem "GHCR manifest PUT failed: $($_.Exception.Message)" ` -Location $CallerName ` -NextSteps @('Verify GITHUB_TOKEN has write:packages scope', "Package: $Package")) } Write-Verbose "Tagged '$ImageRef' as known-good." # ── Return result ──────────────────────────────────────────────────── [PSCustomObject]@{ Action = 'SetKnownGood' Image = $ImageRef Tag = $CurrentTag PreviousKnownGood = $PreviousKnownGood Package = $Package Timestamp = (Get-Date).ToString('o') } } |