1+ # Copyright (c) Microsoft Corporation.
2+ # Licensed under the MIT license.
13# ===========================================================================
24# Integration Test — Extract→Publish Round-Trip
35# ===========================================================================
3133 required : true
3234 type : choice
3335 options :
36+ - Standard
3437 - StandardV2
3538 - Premium
3639 - PremiumV2
7275 required : true
7376 APIM_PUBLISHER_EMAIL :
7477 required : true
78+ APIM_SKU :
79+ required : false
7580
7681permissions :
7782 id-token : write
@@ -84,6 +89,8 @@ concurrency:
8489env :
8590 SOURCE_RG : rg-apiops-bvt-source-${{ github.run_id }}
8691 TARGET_RG : rg-apiops-bvt-target-${{ github.run_id }}
92+ SOURCE_APIM : apiops-bvt-src-${{ github.run_id }}
93+ TARGET_APIM : apiops-bvt-tgt-${{ github.run_id }}
8794
8895jobs :
8996 roundtrip-test :
@@ -103,14 +110,8 @@ jobs:
103110
104111 - run : npm ci && npm run build
105112
106- - name : Azure Login (OIDC)
107- uses : azure/login@v2
108- with :
109- client-id : ${{ secrets.AZURE_CLIENT_ID }}
110- tenant-id : ${{ secrets.AZURE_TENANT_ID }}
111- subscription-id : ${{ secrets.AZURE_SUBSCRIPTION_ID }}
112-
113- - name : Run Round-Trip Test
113+ - name : Resolve Workflow Settings
114+ id : settings
114115 shell : pwsh
115116 run : |
116117 $logLevel = '${{ inputs.log_level }}'
@@ -119,61 +120,140 @@ jobs:
119120 throw "Invalid log_level '$logLevel'. Allowed values: Info, Verbose, Debug."
120121 }
121122
123+ $skuName = '${{ secrets.APIM_SKU }}'
124+ if ([string]::IsNullOrWhiteSpace($skuName)) { $skuName = '${{ inputs.sku }}' }
125+ if ([string]::IsNullOrWhiteSpace($skuName)) { $skuName = 'StandardV2' }
126+
127+ "logLevel=$logLevel" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
128+ "skuName=$skuName" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
129+
130+ - name : Azure Login
131+ uses : azure/login@v2
132+ with :
133+ client-id : ${{ secrets.AZURE_CLIENT_ID }}
134+ tenant-id : ${{ secrets.AZURE_TENANT_ID }}
135+ subscription-id : ${{ secrets.AZURE_SUBSCRIPTION_ID }}
136+
137+ - name : Run Round-Trip Phase 1 (Deploy)
138+ id : phase1
139+ shell : pwsh
140+ run : |
122141 $params = @{
123142 SourceResourceGroup = '${{ env.SOURCE_RG }}'
124143 TargetResourceGroup = '${{ env.TARGET_RG }}'
125- SkuName = '${{ inputs.sku }}'
144+ SourceApimName = '${{ env.SOURCE_APIM }}'
145+ TargetApimName = '${{ env.TARGET_APIM }}'
146+ SourceSubscriptionId = '${{ secrets.AZURE_SUBSCRIPTION_ID }}'
147+ TargetSubscriptionId = '${{ secrets.AZURE_SUBSCRIPTION_ID }}'
148+ SkuName = '${{ steps.settings.outputs.skuName }}'
126149 Location = '${{ inputs.location }}'
127- LogLevel = $ logLevel
150+ LogLevel = '${{ steps.settings.outputs. logLevel }}'
128151 PublisherEmail = '${{ secrets.APIM_PUBLISHER_EMAIL }}'
129- ExtractOutputDir = './extracted-artifacts'
130- HardDelete = $true
131152 }
153+ ./tests/integration/all-resource-types/phases/run-phase1-deploy.ps1 @params
132154
133- ./tests/integration/all-resource-types/run-roundtrip-test.ps1 @params
155+ - name : Azure Login - Refresh Before Phase 2
156+ if : success()
157+ uses : azure/login@v2
158+ with :
159+ # Phase 1 can run for a long time while APIM activates; refresh login
160+ # before extract/publish so apiops receives a fresh federated session.
161+ client-id : ${{ secrets.AZURE_CLIENT_ID }}
162+ tenant-id : ${{ secrets.AZURE_TENANT_ID }}
163+ subscription-id : ${{ secrets.AZURE_SUBSCRIPTION_ID }}
164+
165+ - name : Run Round-Trip Phase 2 (Extract)
166+ id : phase2
167+ if : success()
168+ shell : pwsh
169+ env :
170+ AZURE_CLIENT_ID : ${{ secrets.AZURE_CLIENT_ID }}
171+ AZURE_TENANT_ID : ${{ secrets.AZURE_TENANT_ID }}
172+ run : |
173+ ./tests/integration/all-resource-types/phases/run-phase2-extract.ps1 `
174+ -SourceSubscriptionId '${{ steps.phase1.outputs.sourceSubscriptionId }}' `
175+ -SourceResourceGroup '${{ steps.phase1.outputs.sourceResourceGroup }}' `
176+ -SourceApimName '${{ steps.phase1.outputs.sourceApimName }}' `
177+ -LogLevel '${{ steps.settings.outputs.logLevel }}' `
178+ -ExtractOutputDir './extracted-artifacts'
179+
180+ - name : Run Round-Trip Phase 3 (Validate Extract)
181+ if : success()
182+ shell : pwsh
183+ run : |
184+ $skuName = '${{ steps.phase1.outputs.skuName }}'
185+ $extractOutputDir = '${{ steps.phase2.outputs.ExtractOutputDir }}'
186+
187+ ./tests/integration/all-resource-types/phases/run-phase3-validate-extract.ps1 `
188+ -SkuName $skuName `
189+ -LogLevel '${{ steps.settings.outputs.logLevel }}' `
190+ -ExtractOutputDir $extractOutputDir
191+
192+ - name : Run Round-Trip Phase 4 (Create Overrides)
193+ id : phase4
194+ if : success()
195+ shell : pwsh
196+ run : |
197+ $extractOutputDir = '${{ steps.phase2.outputs.ExtractOutputDir }}'
198+
199+ ./tests/integration/all-resource-types/phases/run-phase4-create-overrides.ps1 `
200+ -TargetSubscriptionId '${{ steps.phase1.outputs.targetSubscriptionId }}' `
201+ -TargetResourceGroup '${{ steps.phase1.outputs.targetResourceGroup }}' `
202+ -LogLevel '${{ steps.settings.outputs.logLevel }}' `
203+ -ExtractOutputDir $extractOutputDir
204+
205+ - name : Run Round-Trip Phase 5 (Publish)
206+ if : success()
207+ shell : pwsh
208+ env :
209+ AZURE_CLIENT_ID : ${{ secrets.AZURE_CLIENT_ID }}
210+ AZURE_TENANT_ID : ${{ secrets.AZURE_TENANT_ID }}
211+ run : |
212+ $extractOutputDir = '${{ steps.phase2.outputs.ExtractOutputDir }}'
213+
214+ ./tests/integration/all-resource-types/phases/run-phase5-publish.ps1 `
215+ -TargetSubscriptionId '${{ steps.phase1.outputs.targetSubscriptionId }}' `
216+ -TargetResourceGroup '${{ steps.phase1.outputs.targetResourceGroup }}' `
217+ -TargetApimName '${{ steps.phase1.outputs.targetApimName }}' `
218+ -OverrideFile '${{ steps.phase4.outputs.overrideFile }}' `
219+ -LogLevel '${{ steps.settings.outputs.logLevel }}' `
220+ -ExtractOutputDir $extractOutputDir
221+
222+ - name : Run Round-Trip Phase 6 (Compare)
223+ if : success()
224+ shell : pwsh
225+ run : |
226+ ./tests/integration/all-resource-types/phases/run-phase6-compare.ps1 `
227+ -SourceSubscriptionId '${{ steps.phase1.outputs.sourceSubscriptionId }}' `
228+ -SourceResourceGroup '${{ steps.phase1.outputs.sourceResourceGroup }}' `
229+ -SourceApimName '${{ steps.phase1.outputs.sourceApimName }}' `
230+ -TargetSubscriptionId '${{ steps.phase1.outputs.targetSubscriptionId }}' `
231+ -TargetResourceGroup '${{ steps.phase1.outputs.targetResourceGroup }}' `
232+ -TargetApimName '${{ steps.phase1.outputs.targetApimName }}' `
233+ -LogLevel '${{ steps.settings.outputs.logLevel }}'
134234
135235 - name : Upload Extracted Artifacts
136236 if : always()
137237 uses : actions/upload-artifact@v4
138238 with :
139- name : extracted-artifacts-${{ inputs.sku }}
140- path : . /extracted-artifacts/
239+ name : extracted-artifacts-${{ steps.phase1.outputs.skuName }}
240+ path : ${{ steps.phase2.outputs.ExtractOutputDir || '. /extracted-artifacts/' }}
141241 if-no-files-found : ignore
142242
143- - name : Emergency Teardown
144- if : failure ()
243+ - name : Run Round-Trip Phase 7 ( Teardown)
244+ if : always ()
145245 shell : pwsh
146246 run : |
147- $sourceRg = '${{ env.SOURCE_RG }}'
148- $targetRg = '${{ env.TARGET_RG }}'
149- $location = '${{ inputs.location }}'
150-
151- # Capture APIM names before RG deletion so we can purge soft-deleted services.
152- $sourceApimName = az apim list --resource-group $sourceRg --query "[0].name" -o tsv 2>$null
153- $targetApimName = az apim list --resource-group $targetRg --query "[0].name" -o tsv 2>$null
154-
155- Write-Host "🚨 Emergency teardown — deleting resource groups..."
156- az group delete --name $sourceRg --yes --no-wait 2>$null
157- az group delete --name $targetRg --yes --no-wait 2>$null
158-
159- Write-Host "⏳ Waiting for resource group deletions before APIM purge..."
160- $maxWaitSeconds = 900
161- $waited = 0
162- $interval = 30
163-
164- while ($waited -lt $maxWaitSeconds) {
165- $srcExists = (az group exists --name $sourceRg -o tsv 2>$null) -eq 'true'
166- $tgtExists = (az group exists --name $targetRg -o tsv 2>$null) -eq 'true'
167- if (-not $srcExists -and -not $tgtExists) {
168- break
169- }
170- Start-Sleep -Seconds $interval
171- $waited += $interval
172- }
247+ $sourceResourceGroup = '${{ steps.phase1.outputs.sourceResourceGroup }}'
248+ if ([string]::IsNullOrWhiteSpace($sourceResourceGroup)) { $sourceResourceGroup = '${{ env.SOURCE_RG }}' }
173249
174- foreach ($apimName in @($sourceApimName, $targetApimName)) {
175- if (-not [string]::IsNullOrWhiteSpace($apimName)) {
176- Write-Host "🗑️ Purging soft-deleted APIM: $apimName"
177- az apim deletedservice purge --service-name $apimName --location $location 2>$null
178- }
179- }
250+ $targetResourceGroup = '${{ steps.phase1.outputs.targetResourceGroup }}'
251+ if ([string]::IsNullOrWhiteSpace($targetResourceGroup)) { $targetResourceGroup = '${{ env.TARGET_RG }}' }
252+
253+ $location = '${{ steps.phase1.outputs.location }}'
254+ if ([string]::IsNullOrWhiteSpace($location)) { $location = '${{ inputs.location }}' }
255+
256+ ./tests/integration/all-resource-types/phases/run-phase7-teardown.ps1 `
257+ -SourceResourceGroup $sourceResourceGroup `
258+ -TargetResourceGroup $targetResourceGroup `
259+ -Location $location
0 commit comments