From 82749c08acbd5d6aeb4de60c1f41c541a69e1db5 Mon Sep 17 00:00:00 2001
From: Anam Navied <anam.naviyou@gmail.com>
Date: Sun, 9 Mar 2025 19:35:41 -0400
Subject: [PATCH 1/4] Attach lifecycle annotations for rolling tags

---
 EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 | 25 +++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 b/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1
index 07df55c65..361510c31 100644
--- a/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1
+++ b/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1
@@ -128,6 +128,9 @@ try {
     $imgJsonFileContent = Get-Content -Path $pathToImgMetadataJson | ConvertFrom-Json
     $images = $imgJsonFileContent.$channel
 
+    # Create variables for lifecycle annotations
+    $endOfLifeDate = Get-Date -Format "yyyy-MM-ddTHH:mm:00Z"
+
     Write-Verbose -Verbose "Push images to ACR"
     foreach ($image in $images)
     {
@@ -151,6 +154,28 @@ try {
                 foreach ($tag in $tags)
                 {
                     Write-Verbose -Verbose "tag: $tag"
+
+                    # check if this rolling tag is associated with an image
+                    $mcrImageFullName = "mcr.microsoft.com/powershell:$tag"
+                    oras manifest fetch $mcrImageFullName > $null
+                    $rollingTagExists = $?
+
+                    if ($rollingTagExists)
+                    {
+                        # If the lineage's rolling tag is already associated with an existing image, attach lifecycle metadata to the existing image to indicate that it is outdated
+                        # Resolve image's digest
+                        $imageDigest = oras resolve $mcrImageName
+
+                        # Import (old) image by digest from MCR into our ACR
+                        $mcrImageNameDigest = "mcr.microsoft.com/powershell@$imageDigest"
+                        $acrEOLImageTag = "$tag-EOL"
+                        az acr import --name $env:DESTINATION_ACR_NAME --source $mcrImageNameDigest --image $acrEOLImageTag
+
+                        # Attach lifecycle annotation, which will eventually get synced to MCR
+                        $acrImageNameDigest = "$env:DESTINATION_ACR_NAME.azurecr.io/public/powershell@$imageDigest"
+                        oras attach --artifact-type "application/vnd.microsoft.artifact.lifecycle" --annotation "vnd.microsoft.artifact.lifecycle.end-of-life.date=$endOfLifeDate" $acrImageNameDigest
+                    }
+
                     # Need to push image for each tag
                     $destination_image_full_name = "$env:DESTINATION_ACR_NAME.azurecr.io/public/powershell:${tag}"
                     Write-Verbose -Verbose "dest img full name: $destination_image_full_name"

From 487e879f0637a7947e5f8193a101d4a95483db1c Mon Sep 17 00:00:00 2001
From: Anam Navied <anam.naviyou@gmail.com>
Date: Mon, 10 Mar 2025 11:57:42 -0400
Subject: [PATCH 2/4] update comment

---
 EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 b/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1
index 361510c31..80f238be0 100644
--- a/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1
+++ b/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1
@@ -162,7 +162,7 @@ try {
 
                     if ($rollingTagExists)
                     {
-                        # If the lineage's rolling tag is already associated with an existing image, attach lifecycle metadata to the existing image to indicate that it is outdated
+                        # If the lineage's rolling tag is already associated with an existing image, then only attach lifecycle metadata to the existing image to indicate that it is outdated
                         # Resolve image's digest
                         $imageDigest = oras resolve $mcrImageName
 

From a53358775a88d10bc7c3460110e08f6bc63b77b0 Mon Sep 17 00:00:00 2001
From: Anam Navied <anam.naviyou@gmail.com>
Date: Thu, 20 Mar 2025 11:52:03 -0400
Subject: [PATCH 3/4] add WhatIf parameter to the pipeline

---
 ...PowerShell-Docker-Image-Build-Official.yml |  7 +++++
 ...werShell-Docker-Image-Release-Official.yml |  7 ++++-
 EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1   | 27 ++++++++++++++++---
 3 files changed, 37 insertions(+), 4 deletions(-)

diff --git a/.pipelines/PowerShell-Docker-Image-Build-Official.yml b/.pipelines/PowerShell-Docker-Image-Build-Official.yml
index 2272dba3c..9fb0ed9a2 100644
--- a/.pipelines/PowerShell-Docker-Image-Build-Official.yml
+++ b/.pipelines/PowerShell-Docker-Image-Build-Official.yml
@@ -5,6 +5,10 @@ parameters:
   default: 'v7.4.0-preview.5'
 - name: 'releaseChannel'
   default: 'preview'
+- name: 'whatIf'
+  displayName: 'Skip publishing to MCR, but show what would get published and attached'
+  type: boolean
+  default: false  
 variables:
 - name: POWERSHELL_TELEMETRY_OPTOUT
   value: 1
@@ -16,6 +20,8 @@ variables:
   value: ${{ parameters.releaseChannel }}
 - name: releaseChannelPath
   value: ''
+- name: whatIf
+  value: ${{ parameters.whatIf }}
 - name: runCodesignValidationInjection
   value: false
 - name: DisableDockerDetector
@@ -129,6 +135,7 @@ extends:
             $info = @{}
             $info.Add("channel", $env:RELEASECHANNEL)
             $info.Add("releaseVersionTag", $env:RELEASEVERSIONTAG)
+            $info.Add("whatIf", $env:WHATIF)
             $info | ConvertTo-Json | Out-File -Encoding utf8NoBOM -FilePath ./BuildInfo.json
           displayName: 'Write build info to file'
         - task: CopyFiles@2
diff --git a/.pipelines/PowerShell-Docker-Image-Release-Official.yml b/.pipelines/PowerShell-Docker-Image-Release-Official.yml
index c3498c8be..29c8fd32c 100644
--- a/.pipelines/PowerShell-Docker-Image-Release-Official.yml
+++ b/.pipelines/PowerShell-Docker-Image-Release-Official.yml
@@ -63,9 +63,11 @@ extends:
             $currentChannel = $($buildMetadata.channel)
             $currentVersion = $($buildMetadata.releaseVersionTag)
             $finalVersion = $currentVersion.Trim("v")
-            Write-Verbose -Verbose "version: $finalVersion and channel: $currentChannel"
+            $whatIfSwitch = $($buildMetadata.whatIf)
+            Write-Verbose -Verbose "version: $finalVersion and channel: $currentChannel and whatIf: $whatIf"
             Write-Host "##vso[task.setvariable variable=channel;isOutput=true]$currentChannel"
             Write-Host "##vso[task.setvariable variable=version;isOutput=true]$finalVersion"
+            Write-Host "##vso[task.setvariable variable=whatIf;isOutput=true]$whatIfSwitch"
           displayName: 'Get channel from buildInfo.json'
           name: setChannelStep
       - job: DownloadImageArtifactFromBuild
@@ -275,6 +277,8 @@ extends:
           value: $[ stageDependencies.GetBuildArtifacts.DownloadMetaArtifactFromBuild.outputs['setChannelStep.channel'] ]
         - name: version
           value: $[ stageDependencies.GetBuildArtifacts.DownloadMetaArtifactFromBuild.outputs['setChannelStep.version'] ]
+        - name: whatIf
+          value: $[ stageDependencies.GetBuildArtifacts.DownloadMetaArtifactFromBuild.outputs['setChannelStep.whatIf'] ]
         pool:
           timeoutInMinutes: 30
           type: windows
@@ -299,6 +303,7 @@ extends:
             $pathToImageMetadataFile = Join-Path -Path $pathToParametersFolder -ChildPath 'ImageMetadata.json'
             $pathToChannelJsonFile = Join-Path -Path $pathToParametersFolder -ChildPath 'ChannelInfo.json'
             $currentChannel = '$(channel)'
+            $currentWhatIfSwitch = '$(whatIf)'
 
             $channelHash = @{channel=$currentChannel}
             $channelHash | ConvertTo-Json | Out-File $pathToChannelJsonFile
diff --git a/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 b/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1
index 80f238be0..e340d22a3 100644
--- a/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1
+++ b/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1
@@ -123,6 +123,7 @@ try {
     Write-Verbose -Verbose "Getting channel info"
     $channelJsonFileContent = Get-Content -Path $pathToChannelJson | ConvertFrom-Json
     $channel = $channelJsonFileContent.channel
+    $whatIf = $channelJsonFileContent.whatIf
 
     Write-Verbose -Verbose "Getting image info"
     $imgJsonFileContent = Get-Content -Path $pathToImgMetadataJson | ConvertFrom-Json
@@ -169,18 +170,38 @@ try {
                         # Import (old) image by digest from MCR into our ACR
                         $mcrImageNameDigest = "mcr.microsoft.com/powershell@$imageDigest"
                         $acrEOLImageTag = "$tag-EOL"
-                        az acr import --name $env:DESTINATION_ACR_NAME --source $mcrImageNameDigest --image $acrEOLImageTag
+                        if (!$whatIf)
+                        {
+                            az acr import --name $env:DESTINATION_ACR_NAME --source $mcrImageNameDigest --image $acrEOLImageTag
+                        }
+                        else {
+                            Write-Verbose -Verbose "az acr import --name $env:DESTINATION_ACR_NAME --source $mcrImageNameDigest --image $acrEOLImageTag"
+                        }
 
                         # Attach lifecycle annotation, which will eventually get synced to MCR
                         $acrImageNameDigest = "$env:DESTINATION_ACR_NAME.azurecr.io/public/powershell@$imageDigest"
-                        oras attach --artifact-type "application/vnd.microsoft.artifact.lifecycle" --annotation "vnd.microsoft.artifact.lifecycle.end-of-life.date=$endOfLifeDate" $acrImageNameDigest
+                        if (!$whatIf)
+                        {
+                            oras attach --artifact-type "application/vnd.microsoft.artifact.lifecycle" --annotation "vnd.microsoft.artifact.lifecycle.end-of-life.date=$endOfLifeDate" $acrImageNameDigest
+                        }
+                        else {
+                            Write-Verbose -Verbose "oras attach --artifact-type `"application/vnd.microsoft.artifact.lifecycle`" --annotation `"vnd.microsoft.artifact.lifecycle.end-of-life.date=$endOfLifeDate`" $acrImageNameDigest"
+                        }
+                        
                     }
 
                     # Need to push image for each tag
                     $destination_image_full_name = "$env:DESTINATION_ACR_NAME.azurecr.io/public/powershell:${tag}"
                     Write-Verbose -Verbose "dest img full name: $destination_image_full_name"
                     Write-Verbose -Verbose "Pushing file $tarballFilePath to $destination_image_full_name"
-                    ./crane push $tarballFilePath $destination_image_full_name
+                    if (!$whatIf)
+                    {
+                        ./crane push $tarballFilePath $destination_image_full_name
+                    }
+                    else {
+                        Write-Verbose "./crane push $tarballFilePath $destination_image_full_name"
+                    }
+                    
                     Write-Verbose -Verbose "done pushing for tag: $tag"
                 }
             }

From 7e12aa77f0c83182d2c7c8873dd529b3f2dd1435 Mon Sep 17 00:00:00 2001
From: Anam Navied <anam.naviyou@gmail.com>
Date: Mon, 24 Mar 2025 12:53:26 -0400
Subject: [PATCH 4/4] add oras discover call to retrieve attached annotation

---
 EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 b/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1
index e340d22a3..edd4c4665 100644
--- a/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1
+++ b/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1
@@ -188,6 +188,16 @@ try {
                             Write-Verbose -Verbose "oras attach --artifact-type `"application/vnd.microsoft.artifact.lifecycle`" --annotation `"vnd.microsoft.artifact.lifecycle.end-of-life.date=$endOfLifeDate`" $acrImageNameDigest"
                         }
                         
+                        if (!$whatIf)
+                        {
+                            $imageAnnotation = oras discover --format json --artifact-type "application/vnd.microsoft.artifact.lifecycle" $acrImageNameDigest
+                            $imageAnnotationJson = $imageAnnotation | ConvertFrom-Json
+                            $eolDateAttached = $imageAnnotationJson.manifests.annotations."vnd.microsoft.artifact.lifecycle.end-of-life.date".ToString("yyyy-MM-ddTHH:mm:00Z")
+                            Write-Verbose -Verbose "date attached: $endOfLifeDate, date found in annotation: $eolDateAttached, match: $($eolDateAttached -eq $endOfLifeDate)"
+                        }
+                        else {
+                            Write-Verbose -Verbose "oras discover --format json --artifact-type `"application/vnd.microsoft.artifact.lifecycle`" $acrImageNameDigest"
+                        }
                     }
 
                     # Need to push image for each tag