tools/scripts/acr-deprecate-releases.ps1 (191 lines of code) (raw):

<# .SYNOPSIS Adds lifecycle annotations to all images int the registry that do not have any yet. .DESCRIPTION The script requires az to be installed and already logged on to a subscription. This means it should be run in a azcliv2 task in the azure pipeline or "az login" must have been performed already. It also requires the oras cli to be installed and available in the path. .PARAMETER Registry The name of the registry where the images need to be annotated. .PARAMETER Subscription The subscription of the registry to use - otherwise uses default #> Param( [string] $Registry = "industrialiotprod", [string] $Subscription = "IOT_GERMANY", [Parameter(Mandatory = $true)] [string] $ReleaseVersion, [String] $Repository ) # Set build subscription if provided if (![string]::IsNullOrEmpty($script:Subscription)) { Write-Debug "Setting subscription to $($script:Subscription)" $argumentList = @("account", "set", "--subscription", $script:Subscription, "-ojson") & "az" @argumentList 2>&1 | ForEach-Object { Write-Host "$_" } if ($LastExitCode -ne 0) { throw "az $($argumentList) failed with $($LastExitCode)." } } # get build registry credentials $argumentList = @("acr", "credential", "show", "--name", $script:Registry, "-ojson") $result = (& "az" @argumentList 2>&1 | ForEach-Object { "$_" }) if ($LastExitCode -ne 0) { throw "az $($argumentList) failed with $($LastExitCode)." } $dockerCredentials = $result | ConvertFrom-Json $dockerUser = $dockerCredentials.username $dockerPassword = $dockerCredentials.passwords[0].value $dockerServer = "$($script:Registry).azurecr.io" $argumentList = @("login", $dockerServer, "--username", $dockerUser, "--password", $dockerPassword) $result = (& "oras" @argumentList 2>&1 | ForEach-Object { "$_" }) if ($LastExitCode -ne 0) { throw "oras $($argumentList) failed with $($LastExitCode)." } # Get build repositories $argumentList = @("acr", "repository", "list", "--name", $script:Registry, "-ojson", "--subscription", $script:Subscription) $result = (& "az" @argumentList 2>&1 | ForEach-Object { "$_" }) if ($LastExitCode -ne 0) { throw "az $($argumentList) failed with $($LastExitCode)." } $repositories = $result | ConvertFrom-Json $ReleaseTags = @() if (![string]::IsNullOrEmpty($script:ReleaseVersion)) { $ReleaseTags += "latest" # Example: if release version is 2.8.1, then base image tags are "2", "2.8", "2.8.1" $versionParts = $script:ReleaseVersion.Split('-')[0].Split('.') if ($versionParts.Count -gt 0) { $versionTag = $versionParts[0] $ReleaseTags += $versionTag for ($i = 1; $i -lt ($versionParts.Count); $i++) { $versionTag = ("$($versionTag).{0}" -f $versionParts[$i]) $ReleaseTags += $versionTag } } } # Perform for each repo foreach ($Repository in $repositories) { if (![string]::IsNullOrEmpty($script:Repository) -and $script:Repository -ne $Repository) { Write-Host "Skipping repository $($Repository) ..." continue } # Get all tags in the repository $argumentList = @("acr", "repository", "show-tags", "--name", $script:Registry, "--subscription", $script:Subscription, "--repository", $Repository, "--orderby", "time_asc", "--detail", "-ojson" ) $result = (& "az" @argumentList 2>&1 | ForEach-Object { "$_" }) if ($LastExitCode -ne 0) { throw "az $($argumentList) failed with $($LastExitCode)." } $images = $result | ConvertFrom-Json # Remove entries with the same digest and keep the latest one $images = $images | Sort-Object -Property createdTime -Descending | Select-Object -Unique -Property digest, name, createdTime if ($images.Count -eq 0) { Write-Host "No images found in $($script:Registry)/$($Repository)." continue } if (![string]::IsNullOrEmpty($script:ReleaseVersion)) { # Check if the release version is in the list of tags $containsReleaseTag = $images.name -contains $script:ReleaseVersion } else { $containsReleaseTag = $false } if ($containsReleaseTag) { Write-Host "Found release version $($script:ReleaseVersion) in $($script:Registry)/$($Repository)." } # For each image in the list, update its expiration date to the "createdTime" # date of the next image in the list except for the last image $imageCount = $images.Count for ($i = 0; $i -lt $imageCount; $i++) { $digest = $images[$i].digest $tag = $images[$i].name $imageName = "$($Repository)@$($digest)" if ($containsReleaseTag) { $isReleaseTag = $ReleaseTags -contains $tag } else { $isReleaseTag = $false } # Check eol artifact referrers exist $argumentList = @("discover", "--artifact-type", "application/vnd.microsoft.artifact.lifecycle", "$($dockerServer)/$($imageName)", "-v", "--output", "json") $result = (& "oras" @argumentList 2>&1 | ForEach-Object { "$_" }) if ($LastExitCode -ne 0) { throw "oras $($argumentList) failed with $($LastExitCode)." } $eol = $result | ConvertFrom-Json $eolDate = ` $eol.manifests.annotations."vnd.microsoft.artifact.lifecycle.end-of-life.date" ` | Select-Object -Unique if ($eolDate.Count -eq 1) { if (!$isReleaseTag) { # Write-Host "Image $($imageName) ($($tag)) has expiration date $($eolDate | ConvertTo-Json)." continue } # delete the eol artifact if it is not a release tag Write-Warning "Removing expiration date $($eolDate | ConvertTo-Json) from released image $($imageName) ($($tag)) ..." } elseif ($eolDate.Count -ne 0) { Write-Warning "$($imageName) ($($tag)) has multiple EOL dates: $($eolDate | ConvertTo-Json)..." Write-Host "Resetting image $($imageName) ($($tag)) expiration date ..." } if ($eol.manifests.Count -gt 0) { # delete all eol artifacts and recreate a correct one foreach ($manifest in $eol.manifests) { $digest = $manifest.digest $eolManifest = "$($Repository)@$($digest)" Write-Host " ... Removing EOL referrer $($dockerServer)/$($eolManifest)." $argumentList = @("acr", "manifest", "delete", "--registry", $script:Registry, "--subscription", $script:Subscription, "--name", $($eolManifest), "--yes") $result = (& "az" @argumentList 2>&1 | ForEach-Object { "$_" }) if ($LastExitCode -ne 0) { throw "az $($argumentList) failed with $($LastExitCode)." } } } if ($isReleaseTag) { continue } elseif ($i -eq ($imageCount - 1)) { # Set expiration date to 1 month from now for last image $expirationDate = (Get-Date).AddMonths(1).ToString("yyyy-MM-ddTHH:mm:ssZ") } else { # Set the expiration date to the created time of the next image $nextImage = $images[$i + 1] if (![string]::IsNullOrEmpty($nextImage.createdTime)) { # parse the date string to a DateTime object and reformat it as string $date = [datetime]::Parse($nextImage.createdTime) $expirationDate = $date.ToString("yyyy-MM-ddTHH:mm:ssZ") } else { $expirationDate = (Get-Date).ToString("yyyy-MM-ddTHH:mm:ssZ") } } $argumentList = @("attach", "--artifact-type", "application/vnd.microsoft.artifact.lifecycle", "--annotation", "vnd.microsoft.artifact.lifecycle.end-of-life.date=$($expirationDate)", "$($dockerServer)/$($imageName)") $result = (& "oras" @argumentList 2>&1 | ForEach-Object { "$_" }) if ($LastExitCode -ne 0) { throw "oras $($argumentList) failed with $($LastExitCode)." } Write-Host "EOL date for $($imageName) ($($tag)) was set to $($expirationDate)." } }