Repository: Azure/azure-sdk-tools
Pull request #10099: Add option to create self contained test resources post script
Author: login: benbp
Labels: name: Central-EngSys
description: This issue is owned by the Engineering System team.
Created: 2025-03-20T22:17:34Z | Merged: 2025-03-26T01:29:06Z

Add option to create self contained test resources post script


Diff:
diff --git a/eng/common/TestResources/New-TestResources.ps1 b/eng/common/TestResources/New-TestResources.ps1
index fce3bd02097..ca2d33fe98f 100755
--- a/eng/common/TestResources/New-TestResources.ps1
+++ b/eng/common/TestResources/New-TestResources.ps1
@@ -93,6 +93,11 @@ param (
     })]
     [array] $AllowIpRanges = @(),
 
+    # Instead of running the post script, create a wrapped file to run it with parameters
+    # so that CI can run it in a subsequent step with a refreshed azure login
+    [Parameter()]
+    [string] $SelfContainedPostScript,
+
     [Parameter()]
     [switch] $CI = ($null -ne $env:SYSTEM_TEAMPROJECTID),
 
@@ -625,9 +630,41 @@ try {
         SetResourceNetworkAccessRules -ResourceGroupName $ResourceGroupName -AllowIpRanges $AllowIpRanges -CI:$CI
 
         $postDeploymentScript = $templateFile.originalFilePath | Split-Path | Join-Path -ChildPath "$ResourceType-resources-post.ps1"
+
+        if ($SelfContainedPostScript -and !(Test-Path $postDeploymentScript)) {
+            throw "-SelfContainedPostScript is not supported if there is no 'test-resources-post.ps1' script in the deployment template directory"
+        }
+
         if (Test-Path $postDeploymentScript) {
-            Log "Invoking post-deployment script '$postDeploymentScript'"
-            &$postDeploymentScript -ResourceGroupName $ResourceGroupName -DeploymentOutputs $deploymentOutputs @PSBoundParameters
+            if ($SelfContainedPostScript) {
+                Log "Creating invokable post-deployment script '$SelfContainedPostScript' from '$postDeploymentScript'"
+
+                $deserialized = @{}
+                foreach ($parameter in $PSBoundParameters.GetEnumerator()) {
+                    if ($parameter.Value -is [System.Management.Automation.SwitchParameter]) {
+                        $deserialized[$parameter.Key] = $parameter.Value.ToBool()
+                    } else {
+                        $deserialized[$parameter.Key] = $parameter.Value
+                    }
+                }
+                $deserialized['ResourceGroupName'] = $ResourceGroupName
+                $deserialized['DeploymentOutputs'] = $deploymentOutputs
+                $serialized = $deserialized | ConvertTo-Json
+
+                $outScript = @"
+`$parameters = `@'
+$serialized
+'`@ | ConvertFrom-Json -AsHashtable
+# Set global variables that aren't always passed as parameters
+`$ResourceGroupName = `$parameters.ResourceGroupName
+`$DeploymentOutputs = `$parameters.DeploymentOutputs
+$postDeploymentScript `@parameters
+"@
+                $outScript | Out-File $SelfContainedPostScript
+            } else {
+                Log "Invoking post-deployment script '$postDeploymentScript'"
+                &$postDeploymentScript -ResourceGroupName $ResourceGroupName -DeploymentOutputs $deploymentOutputs @PSBoundParameters
+            }
         }
 
         if ($templateFile.jsonFilePath.EndsWith('.compiled.json')) {
diff --git a/eng/common/TestResources/deploy-test-resources.yml b/eng/common/TestResources/deploy-test-resources.yml
index 22a232d43e9..7a4593af81e 100644
--- a/eng/common/TestResources/deploy-test-resources.yml
+++ b/eng/common/TestResources/deploy-test-resources.yml
@@ -9,6 +9,7 @@ parameters:
   ResourceType: test
   UseFederatedAuth: true
   PersistOidcToken: false
+  SelfContainedPostScript: self-contained-test-resources-post.ps1
 
 # SubscriptionConfiguration will be splatted into the parameters of the test
 # resources script. It should be JSON in the form:
@@ -89,6 +90,8 @@ steps:
           # Write the new SubscriptionConfiguration to be used by the remove test resources
           Write-Host "##vso[task.setvariable variable=SubscriptionConfiguration;]$($subscriptionConfiguration | ConvertTo-Json -Compress)"
 
+          $postScriptPath = $${{ parameters.PersistOidcToken }} ? '$(Agent.TempDirectory)/${{ parameters.SelfContainedPostScript }}' : $null
+
           # The subscriptionConfiguration may have ArmTemplateParameters defined, so
           # pass those in via the ArmTemplateParameters flag, and handle any
           # additional parameters from the pipelines via AdditionalParameters
@@ -100,10 +103,31 @@ steps:
             @subscriptionConfiguration `
             -AdditionalParameters ${{ parameters.ArmTemplateParameters }} `
             -AllowIpRanges ('$(azsdk-corp-net-ip-ranges)' -split ',') `
+            -SelfContainedPostScript $postScriptPath `
             -CI `
             -Force `
             -Verbose | Out-Null
 
+    - ${{ if eq(parameters.PersistOidcToken, true) }}:
+      # ARM deployments that take longer than 10-15 minutes (e.g. HSM) can
+      # cause post scripts to fail with expired credentials.
+      # Add a new task with a refreshed token as a workaround to this issue.
+      - task: AzureCLI@2
+        displayName: Test Resources Post with refreshed login
+        env:
+          ${{ insert }}: ${{ parameters.EnvVars }}
+        inputs:
+          azureSubscription: ${{ parameters.ServiceConnection }}
+          addSpnToEnvironment: true
+          scriptLocation: inlineScript
+          scriptType: pscore
+          inlineScript: |
+            $env:ARM_OIDC_TOKEN = $env:idToken
+            $scriptPath = '$(Agent.TempDirectory)/${{ parameters.SelfContainedPostScript }}'
+            Write-Host "Executing self contained test resources post script '$scriptPath'"
+            & $scriptPath
+            Remove-Item $scriptPath  # avoid any possible complications when we run multiple deploy templates
+
   - ${{ else }}:
     - pwsh: |
         eng/common/scripts/Import-AzModules.ps1


Reviews:
user: login: weshaggard
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T22:22:58Z
user: login: benbp
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T22:24:17Z
user: login: benbp
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T22:25:02Z
user: login: weshaggard
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T22:32:55Z
user: login: benbp
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T22:43:20Z
user: login: benbp
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T22:47:09Z
user: login: weshaggard
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T18:23:00Z
user: login: weshaggard
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-24T23:17:58Z
user: login: weshaggard
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-24T23:18:07Z

Review comments:
diff_hunk: @@ -626,8 +631,37 @@ try {
 
         $postDeploymentScript = $templateFile.originalFilePath | Split-Path | Join-Path -ChildPath "$ResourceType-resources-post.ps1"
         if (Test-Path $postDeploymentScript) {
-            Log "Invoking post-deployment script '$postDeploymentScript'"
-            &$postDeploymentScript -ResourceGroupName $ResourceGroupName -DeploymentOutputs $deploymentOutputs @PSBoundParameters
+            if ($SelfContainedPostScript) {
+                $selfContainedScript = $templateFile.originalFilePath | Split-Path | Join-Path -ChildPath "self-contained_$ResourceType-resources-post.ps1"
+                Log "Creating invokable post-deployment script '$selfContainedScript' from '$postDeploymentScript'"
+
+                $deserialized = @{}
+                foreach ($parameter in $PSBoundParameters.GetEnumerator()) {
+                    if ($parameter.Value -is [System.Management.Automation.SwitchParameter]) {
+                        $deserialized[$parameter.Key] = $parameter.Value.ToBool()
+                    } else {
+                        $deserialized[$parameter.Key] = $parameter.Value
+                    }
+                }
+                $deserialized['ResourceGroupName'] = $ResourceGroupName
+                $deserialized['DeploymentOutputs'] = $deploymentOutputs
+                $serialized = $deserialized | ConvertTo-Json
+
+                $outScript = @"
+`$parameters = `@'
+$serialized
+'`@ | ConvertFrom-Json -AsHashtable
path: eng/common/TestResources/New-TestResources.ps1
user: login: weshaggard
body: Why serialize in the script as opposed to serializing the entire command line into a string to write to the script?
created_at: 2025-03-20T22:22:58Z
author_association: MEMBER
diff_hunk: @@ -626,8 +631,37 @@ try {
 
         $postDeploymentScript = $templateFile.originalFilePath | Split-Path | Join-Path -ChildPath "$ResourceType-resources-post.ps1"
         if (Test-Path $postDeploymentScript) {
-            Log "Invoking post-deployment script '$postDeploymentScript'"
-            &$postDeploymentScript -ResourceGroupName $ResourceGroupName -DeploymentOutputs $deploymentOutputs @PSBoundParameters
+            if ($SelfContainedPostScript) {
+                $selfContainedScript = $templateFile.originalFilePath | Split-Path | Join-Path -ChildPath "self-contained_$ResourceType-resources-post.ps1"
+                Log "Creating invokable post-deployment script '$selfContainedScript' from '$postDeploymentScript'"
+
+                $deserialized = @{}
+                foreach ($parameter in $PSBoundParameters.GetEnumerator()) {
+                    if ($parameter.Value -is [System.Management.Automation.SwitchParameter]) {
+                        $deserialized[$parameter.Key] = $parameter.Value.ToBool()
+                    } else {
+                        $deserialized[$parameter.Key] = $parameter.Value
+                    }
+                }
+                $deserialized['ResourceGroupName'] = $ResourceGroupName
+                $deserialized['DeploymentOutputs'] = $deploymentOutputs
+                $serialized = $deserialized | ConvertTo-Json
+
+                $outScript = @"
+`$parameters = `@'
+$serialized
+'`@ | ConvertFrom-Json -AsHashtable
path: eng/common/TestResources/New-TestResources.ps1
user: login: benbp
body: The easiest way I can think of to convert instances of arrays/objects and switches into literals is via json serialization.
created_at: 2025-03-20T22:24:17Z
author_association: MEMBER
diff_hunk: @@ -626,8 +631,37 @@ try {
 
         $postDeploymentScript = $templateFile.originalFilePath | Split-Path | Join-Path -ChildPath "$ResourceType-resources-post.ps1"
         if (Test-Path $postDeploymentScript) {
-            Log "Invoking post-deployment script '$postDeploymentScript'"
-            &$postDeploymentScript -ResourceGroupName $ResourceGroupName -DeploymentOutputs $deploymentOutputs @PSBoundParameters
+            if ($SelfContainedPostScript) {
+                $selfContainedScript = $templateFile.originalFilePath | Split-Path | Join-Path -ChildPath "self-contained_$ResourceType-resources-post.ps1"
+                Log "Creating invokable post-deployment script '$selfContainedScript' from '$postDeploymentScript'"
+
+                $deserialized = @{}
+                foreach ($parameter in $PSBoundParameters.GetEnumerator()) {
+                    if ($parameter.Value -is [System.Management.Automation.SwitchParameter]) {
+                        $deserialized[$parameter.Key] = $parameter.Value.ToBool()
+                    } else {
+                        $deserialized[$parameter.Key] = $parameter.Value
+                    }
+                }
+                $deserialized['ResourceGroupName'] = $ResourceGroupName
+                $deserialized['DeploymentOutputs'] = $deploymentOutputs
+                $serialized = $deserialized | ConvertTo-Json
+
+                $outScript = @"
+`$parameters = `@'
+$serialized
+'`@ | ConvertFrom-Json -AsHashtable
path: eng/common/TestResources/New-TestResources.ps1
user: login: benbp
body: Or rather, the json conversion avoids having to "de-compile" runtime powershell into a valid script.
created_at: 2025-03-20T22:25:02Z
author_association: MEMBER
diff_hunk: @@ -93,6 +93,11 @@ param (
     })]
     [array] $AllowIpRanges = @(),
 
+    # Instead of running the post script, create a wrapped file to run it with parameters
+    # so that CI can run it in a subsequent step with a refreshed azure login
+    [Parameter()]
+    [switch] $SelfContainedPostScript,
path: eng/common/TestResources/New-TestResources.ps1
user: login: weshaggard
body: I suspect we need a better name for this as it is I'm not sure I could figure it out by name. Perhaps something that indicates you are writing the post script to a wrapper and let this be the name of that wrappers script so we also don't have to have the magic variable writing here either as it will be an input and the caller can control it.
created_at: 2025-03-20T22:32:55Z
author_association: MEMBER
diff_hunk: @@ -93,6 +93,11 @@ param (
     })]
     [array] $AllowIpRanges = @(),
 
+    # Instead of running the post script, create a wrapped file to run it with parameters
+    # so that CI can run it in a subsequent step with a refreshed azure login
+    [Parameter()]
+    [switch] $SelfContainedPostScript,
path: eng/common/TestResources/New-TestResources.ps1
user: login: benbp
body: Ah ok, that's a simpler approach. So it's only hardcoded in the deploy yaml basically, and not passed around as a var.
created_at: 2025-03-20T22:43:20Z
author_association: MEMBER
diff_hunk: @@ -93,6 +93,11 @@ param (
     })]
     [array] $AllowIpRanges = @(),
 
+    # Instead of running the post script, create a wrapped file to run it with parameters
+    # so that CI can run it in a subsequent step with a refreshed azure login
+    [Parameter()]
+    [switch] $SelfContainedPostScript,
path: eng/common/TestResources/New-TestResources.ps1
user: login: benbp
body: Actually this won't work, because we still need to call the script by it's path, and the path is not just `service directory + name` but rather `service directory + template file directory + name` which is calculated in the test resources script (https://github.com/Azure/azure-sdk-tools/blob/main/eng/common/TestResources/New-TestResources.ps1#L627). Meaning we have to set a variable no matter what in order to get the full path to the script.

I'm not sure if there are any places that this would cause trouble with at the moment, but I figured that the implementation should honor template file path.
created_at: 2025-03-20T22:47:09Z
author_association: MEMBER
diff_hunk: @@ -93,6 +93,11 @@ param (
     })]
     [array] $AllowIpRanges = @(),
 
+    # Instead of running the post script, create a wrapped file to run it with parameters
+    # so that CI can run it in a subsequent step with a refreshed azure login
+    [Parameter()]
+    [switch] $SelfContainedPostScript,
path: eng/common/TestResources/New-TestResources.ps1
user: login: weshaggard
body: Why wouldn't we hard-code the full path in the generated script?
created_at: 2025-03-21T18:23:00Z
author_association: MEMBER
diff_hunk: @@ -626,8 +631,36 @@ try {
 
         $postDeploymentScript = $templateFile.originalFilePath | Split-Path | Join-Path -ChildPath "$ResourceType-resources-post.ps1"
         if (Test-Path $postDeploymentScript) {
-            Log "Invoking post-deployment script '$postDeploymentScript'"
-            &$postDeploymentScript -ResourceGroupName $ResourceGroupName -DeploymentOutputs $deploymentOutputs @PSBoundParameters
+            if ($SelfContainedPostScript) {
+                Log "Creating invokable post-deployment script '$SelfContainedPostScript' from '$postDeploymentScript'"
+
+                $deserialized = @{}
+                foreach ($parameter in $PSBoundParameters.GetEnumerator()) {
+                    if ($parameter.Value -is [System.Management.Automation.SwitchParameter]) {
+                        $deserialized[$parameter.Key] = $parameter.Value.ToBool()
+                    } else {
+                        $deserialized[$parameter.Key] = $parameter.Value
+                    }
+                }
+                $deserialized['ResourceGroupName'] = $ResourceGroupName
+                $deserialized['DeploymentOutputs'] = $deploymentOutputs
+                $serialized = $deserialized | ConvertTo-Json
+
+                $outScript = @"
+`$parameters = `@'
+$serialized
+'`@ | ConvertFrom-Json -AsHashtable
+# Set global variables that aren't always passed as parameters
+`$ResourceGroupName = `$parameters.ResourceGroupName
+`$DeploymentOutputs = `$parameters.DeploymentOutputs
+$postDeploymentScript `@parameters
+"@
+                $outScript | Out-File $SelfContainedPostScript
+                LogVsoCommand "##vso[task.setvariable variable=SELF_CONTAINED_TEST_RESOURCES_POST_SCRIPT;]$selfContainedScript"
path: eng/common/TestResources/New-TestResources.ps1
user: login: weshaggard
body: I assume we don't need this variable any longer. 
created_at: 2025-03-24T23:17:58Z
author_association: MEMBER

PR comments:
user: login: azure-sdk
created_at: 2025-03-20T22:27:37Z
body: The following pipelines have been queued for testing:<br>[java - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4675277)<br>[java - template - tests](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4675278)<br>[js - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4675279)<br>[net - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4675280)<br>[net - template - tests](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4675281)<br>[python - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4675283)<br>[python - template - tests](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4675284) <br>You can sign off on the approval gate to test the release stage of each pipeline. <br>See [eng/common workflow](https://github.com/Azure/azure-sdk-tools/blob/main/eng/common/README.md#workflow)
author_association: COLLABORATOR
user: login: azure-sdk
created_at: 2025-03-21T20:59:21Z
body: The following pipelines have been queued for testing:<br>[java - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679655)<br>[java - template - tests](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679656)<br>[js - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679657)<br>[net - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679661)<br>[net - template - tests](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679665)<br>[python - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679668)<br>[python - template - tests](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679670) <br>You can sign off on the approval gate to test the release stage of each pipeline. <br>See [eng/common workflow](https://github.com/Azure/azure-sdk-tools/blob/main/eng/common/README.md#workflow)
author_association: COLLABORATOR
user: login: azure-sdk
created_at: 2025-03-21T21:19:58Z
body: The following pipelines have been queued for testing:<br>[java - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679767)<br>[java - template - tests](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679768)<br>[js - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679770)<br>[net - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679771)<br>[net - template - tests](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679773)<br>[python - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679787)<br>[python - template - tests](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679790) <br>You can sign off on the approval gate to test the release stage of each pipeline. <br>See [eng/common workflow](https://github.com/Azure/azure-sdk-tools/blob/main/eng/common/README.md#workflow)
author_association: COLLABORATOR
user: login: azure-sdk
created_at: 2025-03-21T21:52:14Z
body: The following pipelines have been queued for testing:<br>[java - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679926)<br>[java - template - tests](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679927)<br>[js - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679928)<br>[net - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679931)<br>[net - template - tests](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679934)<br>[python - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679943)<br>[python - template - tests](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4679945) <br>You can sign off on the approval gate to test the release stage of each pipeline. <br>See [eng/common workflow](https://github.com/Azure/azure-sdk-tools/blob/main/eng/common/README.md#workflow)
author_association: COLLABORATOR
user: login: azure-sdk
created_at: 2025-03-25T23:53:05Z
body: The following pipelines have been queued for testing:<br>[java - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4689853)<br>[java - template - tests](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4689854)<br>[js - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4689856)<br>[net - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4689870)<br>[net - template - tests](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4689874)<br>[python - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4689877)<br>[python - template - tests](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4689878) <br>You can sign off on the approval gate to test the release stage of each pipeline. <br>See [eng/common workflow](https://github.com/Azure/azure-sdk-tools/blob/main/eng/common/README.md#workflow)
author_association: COLLABORATOR
user: login: benbp
created_at: 2025-03-26T00:02:11Z
body: Finally have this working, took a minute to debug with the slow dev/test in pipelines.

Example: https://dev.azure.com/azure-sdk/internal/_build/results?buildId=4689808&view=results
author_association: MEMBER
user: login: azure-sdk
created_at: 2025-03-26T00:21:51Z
body: The following pipelines have been queued for testing:<br>[java - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4690015)<br>[java - template - tests](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4690016)<br>[js - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4690018)<br>[net - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4690027)<br>[net - template - tests](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4690031)<br>[python - template](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4690033)<br>[python - template - tests](https://dev.azure.com/azure-sdk/590cfd2a-581c-4dcb-a12e-6568ce786175/_build/results?buildId=4690035) <br>You can sign off on the approval gate to test the release stage of each pipeline. <br>See [eng/common workflow](https://github.com/Azure/azure-sdk-tools/blob/main/eng/common/README.md#workflow)
author_association: COLLABORATOR