From b56634de36aeb491d26003ef422451640f4ed244 Mon Sep 17 00:00:00 2001 From: spetersenms Date: Tue, 17 Feb 2026 16:52:57 +0100 Subject: [PATCH 01/11] Deployments from PR points to PR ref --- Actions/Deploy/Deploy.ps1 | 16 +++ Actions/Deploy/Deploy.psm1 | 107 ++++++++++++++++++ .../workflows/PublishToEnvironment.yaml | 1 + .../workflows/PublishToEnvironment.yaml | 1 + 4 files changed, 125 insertions(+) diff --git a/Actions/Deploy/Deploy.ps1 b/Actions/Deploy/Deploy.ps1 index 7851dcd5c0..43088efd65 100644 --- a/Actions/Deploy/Deploy.ps1 +++ b/Actions/Deploy/Deploy.ps1 @@ -214,9 +214,25 @@ else { Write-Host "Publishing apps using automation API" Publish-PerTenantExtensionApps @parameters } + + # Track PR deployment against the PR branch + if ($artifactsVersion -like "PR_*") { + $prId = $artifactsVersion.Substring(3) + Set-PRDeploymentTracking -repository $ENV:GITHUB_REPOSITORY -prId $prId -environmentName $environmentName -environmentUrl $environmentUrl -deploymentSuccess $true -token $token + } } } catch { + # Track failed PR deployment + if ($artifactsVersion -like "PR_*") { + try { + $prId = $artifactsVersion.Substring(3) + Set-PRDeploymentTracking -repository $ENV:GITHUB_REPOSITORY -prId $prId -environmentName $environmentName -environmentUrl $environmentUrl -deploymentSuccess $false -token $token + } + catch { + Write-Host "::warning::Failed to track PR deployment: $($_.Exception.Message)" + } + } OutputError -message "Deploying to $environmentName failed.$([environment]::Newline) $($_.Exception.Message)" exit } diff --git a/Actions/Deploy/Deploy.psm1 b/Actions/Deploy/Deploy.psm1 index 900791451d..d91679c568 100644 --- a/Actions/Deploy/Deploy.psm1 +++ b/Actions/Deploy/Deploy.psm1 @@ -29,6 +29,113 @@ function GetHeadRefFromPRId { return $pr.head.ref } +<# + .SYNOPSIS + Track a PR deployment against the PR branch in GitHub. + .DESCRIPTION + When deploying a PR build via PublishToEnvironment from main, GitHub auto-creates a deployment + record against the trigger ref (main). This function deactivates that record and creates a new + deployment against the actual PR branch, so the PR shows correct deployment status. + .PARAMETER repository + The GitHub repository (owner/repo) + .PARAMETER prId + The PR number + .PARAMETER environmentName + The deployment environment name + .PARAMETER environmentUrl + Optional URL for the deployed environment + .PARAMETER deploymentSuccess + Whether the deployment succeeded + .PARAMETER token + The GitHub token (needs deployments:write permission) +#> +function Set-PRDeploymentTracking { + Param( + [Parameter(Mandatory = $true)] + [string] $repository, + [Parameter(Mandatory = $true)] + [string] $prId, + [Parameter(Mandatory = $true)] + [string] $environmentName, + [Parameter(Mandatory = $false)] + [string] $environmentUrl = '', + [Parameter(Mandatory = $false)] + [bool] $deploymentSuccess = $true, + [Parameter(Mandatory = $true)] + [string] $token + ) + + $headers = GetHeaders -token $token + $headers['Content-Type'] = 'application/json' + $apiBase = "https://api.github.com/repos/$repository" + + Write-Host "Tracking deployment for PR #$prId to $environmentName" + + # Get the PR branch ref + $prRef = GetHeadRefFromPRId -repository $repository -prId $prId -token $token + if (-not $prRef) { + Write-Host "::warning::Could not determine PR branch for PR #$prId - skipping deployment tracking" + return + } + Write-Host "PR #$prId branch: $prRef" + + # Deactivate auto-created deployments against the workflow trigger ref for this environment + # The environment: key on the Deploy job auto-creates a deployment against github.ref (e.g. main), + # which is misleading for PR deployments. + try { + $triggerRef = $ENV:GITHUB_REF_NAME + if ($triggerRef -and $triggerRef -ne $prRef) { + $deploymentsUri = "$apiBase/deployments?environment=$environmentName&ref=$triggerRef&per_page=5" + $deploymentsResponse = InvokeWebRequest -Headers $headers -Uri $deploymentsUri + $deployments = $deploymentsResponse.Content | ConvertFrom-Json + foreach ($dep in $deployments) { + Write-Host "Deactivating auto-created deployment $($dep.id) (ref: $($dep.ref))" + $body = '{"state":"inactive"}' + InvokeWebRequest -Headers $headers -Method 'POST' -Uri "$apiBase/deployments/$($dep.id)/statuses" -Body $body | Out-Null + } + } + } + catch { + Write-Host "::warning::Could not deactivate auto-created deployment: $($_.Exception.Message)" + } + + # Create a deployment record against the PR branch + try { + $deploymentBody = @{ + ref = $prRef + environment = $environmentName + auto_merge = $false + required_contexts = @() + description = "Deployed via PublishToEnvironment (PR #$prId)" + } | ConvertTo-Json -Compress + + $deploymentResponse = InvokeWebRequest -Headers $headers -Method 'POST' -Uri "$apiBase/deployments" -Body $deploymentBody + $deployment = $deploymentResponse.Content | ConvertFrom-Json + + if ($deployment.id) { + Write-Host "Created deployment $($deployment.id) against branch $prRef" + + $deployState = if ($deploymentSuccess) { 'success' } else { 'failure' } + $statusBody = @{ + state = $deployState + environment = $environmentName + description = "Deployed PR #$prId to $environmentName" + } + if ($environmentUrl) { $statusBody['environment_url'] = $environmentUrl } + $statusBodyJson = $statusBody | ConvertTo-Json -Compress + + InvokeWebRequest -Headers $headers -Method 'POST' -Uri "$apiBase/deployments/$($deployment.id)/statuses" -Body $statusBodyJson | Out-Null + Write-Host "Deployment status set to $deployState" + } + else { + Write-Host "::warning::Failed to create deployment record for PR #$prId" + } + } + catch { + Write-Host "::warning::Failed to create PR deployment record: $($_.Exception.Message)" + } +} + <# .SYNOPSIS Get apps and dependencies from artifacts diff --git a/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml b/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml index 710d401195..1be70f1850 100644 --- a/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml +++ b/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml @@ -18,6 +18,7 @@ on: permissions: actions: read contents: read + deployments: write id-token: write pull-requests: read checks: read diff --git a/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml b/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml index 710d401195..1be70f1850 100644 --- a/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml +++ b/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml @@ -18,6 +18,7 @@ on: permissions: actions: read contents: read + deployments: write id-token: write pull-requests: read checks: read From 8f37988c329c53831f356e161d9817b1047f02df Mon Sep 17 00:00:00 2001 From: spetersenms Date: Wed, 18 Feb 2026 12:05:18 +0100 Subject: [PATCH 02/11] Moved environment tag logic to after deploymeny job. --- Actions/Deploy/Deploy.ps1 | 15 --- Actions/Deploy/Deploy.psm1 | 107 ------------------ .../workflows/PublishToEnvironment.yaml | 71 ++++++++++++ .../workflows/PublishToEnvironment.yaml | 71 ++++++++++++ 4 files changed, 142 insertions(+), 122 deletions(-) diff --git a/Actions/Deploy/Deploy.ps1 b/Actions/Deploy/Deploy.ps1 index 43088efd65..174fed9109 100644 --- a/Actions/Deploy/Deploy.ps1 +++ b/Actions/Deploy/Deploy.ps1 @@ -215,24 +215,9 @@ else { Publish-PerTenantExtensionApps @parameters } - # Track PR deployment against the PR branch - if ($artifactsVersion -like "PR_*") { - $prId = $artifactsVersion.Substring(3) - Set-PRDeploymentTracking -repository $ENV:GITHUB_REPOSITORY -prId $prId -environmentName $environmentName -environmentUrl $environmentUrl -deploymentSuccess $true -token $token - } } } catch { - # Track failed PR deployment - if ($artifactsVersion -like "PR_*") { - try { - $prId = $artifactsVersion.Substring(3) - Set-PRDeploymentTracking -repository $ENV:GITHUB_REPOSITORY -prId $prId -environmentName $environmentName -environmentUrl $environmentUrl -deploymentSuccess $false -token $token - } - catch { - Write-Host "::warning::Failed to track PR deployment: $($_.Exception.Message)" - } - } OutputError -message "Deploying to $environmentName failed.$([environment]::Newline) $($_.Exception.Message)" exit } diff --git a/Actions/Deploy/Deploy.psm1 b/Actions/Deploy/Deploy.psm1 index d91679c568..900791451d 100644 --- a/Actions/Deploy/Deploy.psm1 +++ b/Actions/Deploy/Deploy.psm1 @@ -29,113 +29,6 @@ function GetHeadRefFromPRId { return $pr.head.ref } -<# - .SYNOPSIS - Track a PR deployment against the PR branch in GitHub. - .DESCRIPTION - When deploying a PR build via PublishToEnvironment from main, GitHub auto-creates a deployment - record against the trigger ref (main). This function deactivates that record and creates a new - deployment against the actual PR branch, so the PR shows correct deployment status. - .PARAMETER repository - The GitHub repository (owner/repo) - .PARAMETER prId - The PR number - .PARAMETER environmentName - The deployment environment name - .PARAMETER environmentUrl - Optional URL for the deployed environment - .PARAMETER deploymentSuccess - Whether the deployment succeeded - .PARAMETER token - The GitHub token (needs deployments:write permission) -#> -function Set-PRDeploymentTracking { - Param( - [Parameter(Mandatory = $true)] - [string] $repository, - [Parameter(Mandatory = $true)] - [string] $prId, - [Parameter(Mandatory = $true)] - [string] $environmentName, - [Parameter(Mandatory = $false)] - [string] $environmentUrl = '', - [Parameter(Mandatory = $false)] - [bool] $deploymentSuccess = $true, - [Parameter(Mandatory = $true)] - [string] $token - ) - - $headers = GetHeaders -token $token - $headers['Content-Type'] = 'application/json' - $apiBase = "https://api.github.com/repos/$repository" - - Write-Host "Tracking deployment for PR #$prId to $environmentName" - - # Get the PR branch ref - $prRef = GetHeadRefFromPRId -repository $repository -prId $prId -token $token - if (-not $prRef) { - Write-Host "::warning::Could not determine PR branch for PR #$prId - skipping deployment tracking" - return - } - Write-Host "PR #$prId branch: $prRef" - - # Deactivate auto-created deployments against the workflow trigger ref for this environment - # The environment: key on the Deploy job auto-creates a deployment against github.ref (e.g. main), - # which is misleading for PR deployments. - try { - $triggerRef = $ENV:GITHUB_REF_NAME - if ($triggerRef -and $triggerRef -ne $prRef) { - $deploymentsUri = "$apiBase/deployments?environment=$environmentName&ref=$triggerRef&per_page=5" - $deploymentsResponse = InvokeWebRequest -Headers $headers -Uri $deploymentsUri - $deployments = $deploymentsResponse.Content | ConvertFrom-Json - foreach ($dep in $deployments) { - Write-Host "Deactivating auto-created deployment $($dep.id) (ref: $($dep.ref))" - $body = '{"state":"inactive"}' - InvokeWebRequest -Headers $headers -Method 'POST' -Uri "$apiBase/deployments/$($dep.id)/statuses" -Body $body | Out-Null - } - } - } - catch { - Write-Host "::warning::Could not deactivate auto-created deployment: $($_.Exception.Message)" - } - - # Create a deployment record against the PR branch - try { - $deploymentBody = @{ - ref = $prRef - environment = $environmentName - auto_merge = $false - required_contexts = @() - description = "Deployed via PublishToEnvironment (PR #$prId)" - } | ConvertTo-Json -Compress - - $deploymentResponse = InvokeWebRequest -Headers $headers -Method 'POST' -Uri "$apiBase/deployments" -Body $deploymentBody - $deployment = $deploymentResponse.Content | ConvertFrom-Json - - if ($deployment.id) { - Write-Host "Created deployment $($deployment.id) against branch $prRef" - - $deployState = if ($deploymentSuccess) { 'success' } else { 'failure' } - $statusBody = @{ - state = $deployState - environment = $environmentName - description = "Deployed PR #$prId to $environmentName" - } - if ($environmentUrl) { $statusBody['environment_url'] = $environmentUrl } - $statusBodyJson = $statusBody | ConvertTo-Json -Compress - - InvokeWebRequest -Headers $headers -Method 'POST' -Uri "$apiBase/deployments/$($deployment.id)/statuses" -Body $statusBodyJson | Out-Null - Write-Host "Deployment status set to $deployState" - } - else { - Write-Host "::warning::Failed to create deployment record for PR #$prId" - } - } - catch { - Write-Host "::warning::Failed to create PR deployment record: $($_.Exception.Message)" - } -} - <# .SYNOPSIS Get apps and dependencies from artifacts diff --git a/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml b/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml index 1be70f1850..6c434a9916 100644 --- a/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml +++ b/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml @@ -195,6 +195,77 @@ jobs: artifactsFolder: '.artifacts' deploymentEnvironmentsJson: ${{ needs.Initialization.outputs.deploymentEnvironmentsJson }} + TrackPRDeployment: + needs: [ Initialization, Deploy ] + if: always() && (needs.Deploy.result == 'success' || needs.Deploy.result == 'failure') && startsWith(github.event.inputs.appVersion, 'PR_') + runs-on: [ windows-latest ] + steps: + - name: Track PR Deployment + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + $prNumber = '${{ github.event.inputs.appVersion }}'.Substring(3) + $repo = '${{ github.repository }}' + $deployResult = '${{ needs.Deploy.result }}' + $state = if ($deployResult -eq 'success') { 'success' } else { 'failure' } + $headers = @{ + "Authorization" = "Bearer $env:GITHUB_TOKEN" + "Accept" = "application/vnd.github+json" + } + # Get PR branch ref + $pr = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/pulls/$prNumber" -Headers $headers + $prRef = $pr.head.ref + Write-Host "PR #$prNumber branch: $prRef" + # Get environments from Initialization + $matrixJson = '${{ needs.Initialization.outputs.environmentsMatrixJson }}' + $matrix = $matrixJson | ConvertFrom-Json + $environments = @($matrix.matrix.include | ForEach-Object { $_.environment }) + foreach ($envName in $environments) { + Write-Host "Tracking deployment for environment: $envName" + # Find auto-created deployment to retrieve environment URL + $envUrl = $null + try { + $encodedEnv = [System.Uri]::EscapeDataString($envName) + $existingDeps = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments?environment=$encodedEnv&ref=${{ github.ref_name }}&per_page=1" -Headers $headers + if ($existingDeps -and @($existingDeps).Count -gt 0) { + $depId = @($existingDeps)[0].id + $statuses = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$depId/statuses?per_page=1" -Headers $headers + if ($statuses -and @($statuses).Count -gt 0 -and @($statuses)[0].environment_url) { + $envUrl = @($statuses)[0].environment_url + } + } + } + catch { + Write-Host "::warning::Could not retrieve auto-created deployment info: $($_.Exception.Message)" + } + # Create deployment against the PR branch + $deployBody = @{ + ref = $prRef + environment = $envName + auto_merge = $false + required_contexts = @() + description = "Deployed via PublishToEnvironment (PR #$prNumber)" + } | ConvertTo-Json -Compress + try { + $deployment = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments" -Method Post -Headers $headers -Body $deployBody -ContentType 'application/json' + Write-Host "Created deployment $($deployment.id) against $prRef" + # Set deployment status — auto_inactive will deactivate the auto-created main deployment + $statusBody = @{ + state = $state + environment = $envName + description = "Deployed PR #$prNumber to $envName" + } + if ($envUrl) { $statusBody['environment_url'] = $envUrl } + $statusJson = $statusBody | ConvertTo-Json -Compress + Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$($deployment.id)/statuses" -Method Post -Headers $headers -Body $statusJson -ContentType 'application/json' | Out-Null + Write-Host "Deployment status set to $state for $envName" + } + catch { + Write-Host "::warning::Failed to create PR deployment for environment $envName`: $($_.Exception.Message)" + } + } + PostProcess: needs: [ Initialization, Deploy ] if: always() diff --git a/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml b/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml index 1be70f1850..6c434a9916 100644 --- a/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml +++ b/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml @@ -195,6 +195,77 @@ jobs: artifactsFolder: '.artifacts' deploymentEnvironmentsJson: ${{ needs.Initialization.outputs.deploymentEnvironmentsJson }} + TrackPRDeployment: + needs: [ Initialization, Deploy ] + if: always() && (needs.Deploy.result == 'success' || needs.Deploy.result == 'failure') && startsWith(github.event.inputs.appVersion, 'PR_') + runs-on: [ windows-latest ] + steps: + - name: Track PR Deployment + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + $prNumber = '${{ github.event.inputs.appVersion }}'.Substring(3) + $repo = '${{ github.repository }}' + $deployResult = '${{ needs.Deploy.result }}' + $state = if ($deployResult -eq 'success') { 'success' } else { 'failure' } + $headers = @{ + "Authorization" = "Bearer $env:GITHUB_TOKEN" + "Accept" = "application/vnd.github+json" + } + # Get PR branch ref + $pr = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/pulls/$prNumber" -Headers $headers + $prRef = $pr.head.ref + Write-Host "PR #$prNumber branch: $prRef" + # Get environments from Initialization + $matrixJson = '${{ needs.Initialization.outputs.environmentsMatrixJson }}' + $matrix = $matrixJson | ConvertFrom-Json + $environments = @($matrix.matrix.include | ForEach-Object { $_.environment }) + foreach ($envName in $environments) { + Write-Host "Tracking deployment for environment: $envName" + # Find auto-created deployment to retrieve environment URL + $envUrl = $null + try { + $encodedEnv = [System.Uri]::EscapeDataString($envName) + $existingDeps = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments?environment=$encodedEnv&ref=${{ github.ref_name }}&per_page=1" -Headers $headers + if ($existingDeps -and @($existingDeps).Count -gt 0) { + $depId = @($existingDeps)[0].id + $statuses = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$depId/statuses?per_page=1" -Headers $headers + if ($statuses -and @($statuses).Count -gt 0 -and @($statuses)[0].environment_url) { + $envUrl = @($statuses)[0].environment_url + } + } + } + catch { + Write-Host "::warning::Could not retrieve auto-created deployment info: $($_.Exception.Message)" + } + # Create deployment against the PR branch + $deployBody = @{ + ref = $prRef + environment = $envName + auto_merge = $false + required_contexts = @() + description = "Deployed via PublishToEnvironment (PR #$prNumber)" + } | ConvertTo-Json -Compress + try { + $deployment = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments" -Method Post -Headers $headers -Body $deployBody -ContentType 'application/json' + Write-Host "Created deployment $($deployment.id) against $prRef" + # Set deployment status — auto_inactive will deactivate the auto-created main deployment + $statusBody = @{ + state = $state + environment = $envName + description = "Deployed PR #$prNumber to $envName" + } + if ($envUrl) { $statusBody['environment_url'] = $envUrl } + $statusJson = $statusBody | ConvertTo-Json -Compress + Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$($deployment.id)/statuses" -Method Post -Headers $headers -Body $statusJson -ContentType 'application/json' | Out-Null + Write-Host "Deployment status set to $state for $envName" + } + catch { + Write-Host "::warning::Failed to create PR deployment for environment $envName`: $($_.Exception.Message)" + } + } + PostProcess: needs: [ Initialization, Deploy ] if: always() From 96ecce5b36fc1ed9ff0fa0fb61e529ed5f2f743f Mon Sep 17 00:00:00 2001 From: spetersenms Date: Wed, 18 Feb 2026 15:37:41 +0100 Subject: [PATCH 03/11] Trying to disable main deployment --- .../workflows/PublishToEnvironment.yaml | 25 +++++++++++-------- .../workflows/PublishToEnvironment.yaml | 25 +++++++++++-------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml b/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml index 6c434a9916..56106cefb7 100644 --- a/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml +++ b/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml @@ -223,21 +223,27 @@ jobs: $environments = @($matrix.matrix.include | ForEach-Object { $_.environment }) foreach ($envName in $environments) { Write-Host "Tracking deployment for environment: $envName" - # Find auto-created deployment to retrieve environment URL + $encodedEnv = [System.Uri]::EscapeDataString($envName) + # Find the auto-created deployment (ref: main) to get its environment URL and then deactivate it $envUrl = $null try { - $encodedEnv = [System.Uri]::EscapeDataString($envName) - $existingDeps = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments?environment=$encodedEnv&ref=${{ github.ref_name }}&per_page=1" -Headers $headers - if ($existingDeps -and @($existingDeps).Count -gt 0) { - $depId = @($existingDeps)[0].id - $statuses = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$depId/statuses?per_page=1" -Headers $headers - if ($statuses -and @($statuses).Count -gt 0 -and @($statuses)[0].environment_url) { - $envUrl = @($statuses)[0].environment_url + $existingDeps = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments?environment=$encodedEnv&ref=${{ github.ref_name }}&per_page=5" -Headers $headers + foreach ($dep in @($existingDeps)) { + # Retrieve environment URL from the auto-created deployment + if (-not $envUrl) { + $statuses = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$($dep.id)/statuses?per_page=1" -Headers $headers + if ($statuses -and @($statuses).Count -gt 0 -and @($statuses)[0].environment_url) { + $envUrl = @($statuses)[0].environment_url + } } + # Deactivate the auto-created deployment so it no longer shows as active + Write-Host "Deactivating auto-created deployment $($dep.id) (ref: $($dep.ref))" + $inactiveBody = '{"state":"inactive"}' + Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$($dep.id)/statuses" -Method Post -Headers $headers -Body $inactiveBody -ContentType 'application/json' | Out-Null } } catch { - Write-Host "::warning::Could not retrieve auto-created deployment info: $($_.Exception.Message)" + Write-Host "::warning::Could not process auto-created deployment: $($_.Exception.Message)" } # Create deployment against the PR branch $deployBody = @{ @@ -250,7 +256,6 @@ jobs: try { $deployment = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments" -Method Post -Headers $headers -Body $deployBody -ContentType 'application/json' Write-Host "Created deployment $($deployment.id) against $prRef" - # Set deployment status — auto_inactive will deactivate the auto-created main deployment $statusBody = @{ state = $state environment = $envName diff --git a/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml b/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml index 6c434a9916..56106cefb7 100644 --- a/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml +++ b/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml @@ -223,21 +223,27 @@ jobs: $environments = @($matrix.matrix.include | ForEach-Object { $_.environment }) foreach ($envName in $environments) { Write-Host "Tracking deployment for environment: $envName" - # Find auto-created deployment to retrieve environment URL + $encodedEnv = [System.Uri]::EscapeDataString($envName) + # Find the auto-created deployment (ref: main) to get its environment URL and then deactivate it $envUrl = $null try { - $encodedEnv = [System.Uri]::EscapeDataString($envName) - $existingDeps = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments?environment=$encodedEnv&ref=${{ github.ref_name }}&per_page=1" -Headers $headers - if ($existingDeps -and @($existingDeps).Count -gt 0) { - $depId = @($existingDeps)[0].id - $statuses = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$depId/statuses?per_page=1" -Headers $headers - if ($statuses -and @($statuses).Count -gt 0 -and @($statuses)[0].environment_url) { - $envUrl = @($statuses)[0].environment_url + $existingDeps = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments?environment=$encodedEnv&ref=${{ github.ref_name }}&per_page=5" -Headers $headers + foreach ($dep in @($existingDeps)) { + # Retrieve environment URL from the auto-created deployment + if (-not $envUrl) { + $statuses = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$($dep.id)/statuses?per_page=1" -Headers $headers + if ($statuses -and @($statuses).Count -gt 0 -and @($statuses)[0].environment_url) { + $envUrl = @($statuses)[0].environment_url + } } + # Deactivate the auto-created deployment so it no longer shows as active + Write-Host "Deactivating auto-created deployment $($dep.id) (ref: $($dep.ref))" + $inactiveBody = '{"state":"inactive"}' + Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$($dep.id)/statuses" -Method Post -Headers $headers -Body $inactiveBody -ContentType 'application/json' | Out-Null } } catch { - Write-Host "::warning::Could not retrieve auto-created deployment info: $($_.Exception.Message)" + Write-Host "::warning::Could not process auto-created deployment: $($_.Exception.Message)" } # Create deployment against the PR branch $deployBody = @{ @@ -250,7 +256,6 @@ jobs: try { $deployment = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments" -Method Post -Headers $headers -Body $deployBody -ContentType 'application/json' Write-Host "Created deployment $($deployment.id) against $prRef" - # Set deployment status — auto_inactive will deactivate the auto-created main deployment $statusBody = @{ state = $state environment = $envName From 74e9506c53ecfe01efe87983ac42fa08cbca4c13 Mon Sep 17 00:00:00 2001 From: spetersenms Date: Thu, 19 Feb 2026 16:26:06 +0100 Subject: [PATCH 04/11] New action to track PR deployments correctly. --- Actions/TrackPRDeployment/README.md | 25 ++++++ .../TrackPRDeployment/TrackPRDeployment.ps1 | 88 +++++++++++++++++++ Actions/TrackPRDeployment/action.yaml | 37 ++++++++ 3 files changed, 150 insertions(+) create mode 100644 Actions/TrackPRDeployment/README.md create mode 100644 Actions/TrackPRDeployment/TrackPRDeployment.ps1 create mode 100644 Actions/TrackPRDeployment/action.yaml diff --git a/Actions/TrackPRDeployment/README.md b/Actions/TrackPRDeployment/README.md new file mode 100644 index 0000000000..4210056212 --- /dev/null +++ b/Actions/TrackPRDeployment/README.md @@ -0,0 +1,25 @@ +# TrackPRDeployment + +Track PR deployments against the PR branch instead of the trigger branch (e.g. main). + +When deploying a PR build via PublishToEnvironment, the workflow runs on main but deploys artifacts built from a PR branch. GitHub's `environment:` key auto-creates a deployment record against main, which is misleading. This action deactivates that record and creates a new deployment against the actual PR branch, so the deployment shows correctly on the PR. + +## INPUT + +### ENV variables + +None + +### Parameters + +| Name | Required | Description | Default value | +| :-- | :-: | :-- | :-- | +| shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | +| token | | The GitHub token running the action | github.token | +| environmentsMatrixJson | Yes | JSON string with the environments matrix from Initialization | | +| deployResult | Yes | The result of the Deploy job (success or failure) | | +| artifactsVersion | Yes | Artifacts version (PR_\) | | + +## OUTPUT + +None diff --git a/Actions/TrackPRDeployment/TrackPRDeployment.ps1 b/Actions/TrackPRDeployment/TrackPRDeployment.ps1 new file mode 100644 index 0000000000..c3c0f4167f --- /dev/null +++ b/Actions/TrackPRDeployment/TrackPRDeployment.ps1 @@ -0,0 +1,88 @@ +Param( + [Parameter(Mandatory = $false)] + [string] $token = $ENV:GITHUB_TOKEN, + [Parameter(Mandatory = $true)] + [string] $environmentsMatrixJson, + [Parameter(Mandatory = $true)] + [string] $deployResult, + [Parameter(Mandatory = $true)] + [string] $artifactsVersion +) + +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + +# Extract PR number from artifactsVersion (format: PR_) +$prNumber = $artifactsVersion.Substring(3) +$repo = $ENV:GITHUB_REPOSITORY +$triggerRef = $ENV:GITHUB_REF_NAME +$state = if ($deployResult -eq 'success') { 'success' } else { 'failure' } + +$headers = @{ + "Authorization" = "Bearer $token" + "Accept" = "application/vnd.github+json" +} + +# Get PR branch ref +Write-Host "Resolving PR #$prNumber branch ref" +$pr = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/pulls/$prNumber" -Headers $headers +$prRef = $pr.head.ref +Write-Host "PR #$prNumber branch: $prRef" + +# Parse environments from the matrix JSON +$matrix = $environmentsMatrixJson | ConvertFrom-Json +$environments = @($matrix.matrix.include | ForEach-Object { $_.environment }) + +foreach ($envName in $environments) { + Write-Host "Tracking deployment for environment: $envName" + $encodedEnv = [System.Uri]::EscapeDataString($envName) + + # Find the auto-created deployment (ref: trigger branch) to get its environment URL and then deactivate it + $envUrl = $null + try { + $existingDeps = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments?environment=$encodedEnv&ref=$triggerRef&per_page=5" -Headers $headers + foreach ($dep in @($existingDeps)) { + # Retrieve environment URL from the auto-created deployment + if (-not $envUrl) { + $statuses = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$($dep.id)/statuses?per_page=1" -Headers $headers + if ($statuses -and @($statuses).Count -gt 0 -and @($statuses)[0].environment_url) { + $envUrl = @($statuses)[0].environment_url + } + } + # Deactivate the auto-created deployment so it no longer shows as active + Write-Host "Deactivating auto-created deployment $($dep.id) (ref: $($dep.ref))" + $inactiveBody = '{"state":"inactive"}' + Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$($dep.id)/statuses" -Method Post -Headers $headers -Body $inactiveBody -ContentType 'application/json' | Out-Null + } + } + catch { + Write-Host "::warning::Could not process auto-created deployment: $($_.Exception.Message)" + } + + # Create deployment against the PR branch + $deployBody = @{ + ref = $prRef + environment = $envName + auto_merge = $false + required_contexts = @() + description = "Deployed via PublishToEnvironment (PR #$prNumber)" + } | ConvertTo-Json -Compress + + try { + $deployment = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments" -Method Post -Headers $headers -Body $deployBody -ContentType 'application/json' + Write-Host "Created deployment $($deployment.id) against $prRef" + + $statusBody = @{ + state = $state + environment = $envName + description = "Deployed PR #$prNumber to $envName" + } + if ($envUrl) { $statusBody['environment_url'] = $envUrl } + $statusJson = $statusBody | ConvertTo-Json -Compress + + Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$($deployment.id)/statuses" -Method Post -Headers $headers -Body $statusJson -ContentType 'application/json' | Out-Null + Write-Host "Deployment status set to $state for $envName" + } + catch { + Write-Host "::warning::Failed to create PR deployment for environment $envName`: $($_.Exception.Message)" + } +} diff --git a/Actions/TrackPRDeployment/action.yaml b/Actions/TrackPRDeployment/action.yaml new file mode 100644 index 0000000000..1884b62983 --- /dev/null +++ b/Actions/TrackPRDeployment/action.yaml @@ -0,0 +1,37 @@ +name: TrackPRDeployment +author: Microsoft Corporation +inputs: + shell: + description: Shell in which you want to run the action (powershell or pwsh) + required: false + default: powershell + token: + description: The GitHub token running the action + required: false + default: ${{ github.token }} + environmentsMatrixJson: + description: JSON string with the environments matrix from Initialization + required: true + deployResult: + description: The result of the Deploy job (success or failure) + required: true + artifactsVersion: + description: Artifacts version (PR_) + required: true +runs: + using: composite + steps: + - name: run + shell: ${{ inputs.shell }} + env: + _token: ${{ inputs.token }} + _environmentsMatrixJson: ${{ inputs.environmentsMatrixJson }} + _deployResult: ${{ inputs.deployResult }} + _artifactsVersion: ${{ inputs.artifactsVersion }} + run: | + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "TrackPRDeployment" -Action { + ${{ github.action_path }}/TrackPRDeployment.ps1 -token $ENV:_token -environmentsMatrixJson $ENV:_environmentsMatrixJson -deployResult $ENV:_deployResult -artifactsVersion $ENV:_artifactsVersion + } +branding: + icon: terminal + color: blue From 82c783cd7be7833a8075939820086209690a9d93 Mon Sep 17 00:00:00 2001 From: spetersenms Date: Thu, 19 Feb 2026 16:26:23 +0100 Subject: [PATCH 05/11] Using new tracking action --- .../workflows/PublishToEnvironment.yaml | 78 +++---------------- .../workflows/PublishToEnvironment.yaml | 78 +++---------------- 2 files changed, 18 insertions(+), 138 deletions(-) diff --git a/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml b/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml index 56106cefb7..4db4e814d0 100644 --- a/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml +++ b/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml @@ -200,76 +200,16 @@ jobs: if: always() && (needs.Deploy.result == 'success' || needs.Deploy.result == 'failure') && startsWith(github.event.inputs.appVersion, 'PR_') runs-on: [ windows-latest ] steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Track PR Deployment - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - $prNumber = '${{ github.event.inputs.appVersion }}'.Substring(3) - $repo = '${{ github.repository }}' - $deployResult = '${{ needs.Deploy.result }}' - $state = if ($deployResult -eq 'success') { 'success' } else { 'failure' } - $headers = @{ - "Authorization" = "Bearer $env:GITHUB_TOKEN" - "Accept" = "application/vnd.github+json" - } - # Get PR branch ref - $pr = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/pulls/$prNumber" -Headers $headers - $prRef = $pr.head.ref - Write-Host "PR #$prNumber branch: $prRef" - # Get environments from Initialization - $matrixJson = '${{ needs.Initialization.outputs.environmentsMatrixJson }}' - $matrix = $matrixJson | ConvertFrom-Json - $environments = @($matrix.matrix.include | ForEach-Object { $_.environment }) - foreach ($envName in $environments) { - Write-Host "Tracking deployment for environment: $envName" - $encodedEnv = [System.Uri]::EscapeDataString($envName) - # Find the auto-created deployment (ref: main) to get its environment URL and then deactivate it - $envUrl = $null - try { - $existingDeps = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments?environment=$encodedEnv&ref=${{ github.ref_name }}&per_page=5" -Headers $headers - foreach ($dep in @($existingDeps)) { - # Retrieve environment URL from the auto-created deployment - if (-not $envUrl) { - $statuses = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$($dep.id)/statuses?per_page=1" -Headers $headers - if ($statuses -and @($statuses).Count -gt 0 -and @($statuses)[0].environment_url) { - $envUrl = @($statuses)[0].environment_url - } - } - # Deactivate the auto-created deployment so it no longer shows as active - Write-Host "Deactivating auto-created deployment $($dep.id) (ref: $($dep.ref))" - $inactiveBody = '{"state":"inactive"}' - Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$($dep.id)/statuses" -Method Post -Headers $headers -Body $inactiveBody -ContentType 'application/json' | Out-Null - } - } - catch { - Write-Host "::warning::Could not process auto-created deployment: $($_.Exception.Message)" - } - # Create deployment against the PR branch - $deployBody = @{ - ref = $prRef - environment = $envName - auto_merge = $false - required_contexts = @() - description = "Deployed via PublishToEnvironment (PR #$prNumber)" - } | ConvertTo-Json -Compress - try { - $deployment = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments" -Method Post -Headers $headers -Body $deployBody -ContentType 'application/json' - Write-Host "Created deployment $($deployment.id) against $prRef" - $statusBody = @{ - state = $state - environment = $envName - description = "Deployed PR #$prNumber to $envName" - } - if ($envUrl) { $statusBody['environment_url'] = $envUrl } - $statusJson = $statusBody | ConvertTo-Json -Compress - Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$($deployment.id)/statuses" -Method Post -Headers $headers -Body $statusJson -ContentType 'application/json' | Out-Null - Write-Host "Deployment status set to $state for $envName" - } - catch { - Write-Host "::warning::Failed to create PR deployment for environment $envName`: $($_.Exception.Message)" - } - } + uses: microsoft/AL-Go-Actions/TrackPRDeployment@main + with: + shell: powershell + environmentsMatrixJson: ${{ needs.Initialization.outputs.environmentsMatrixJson }} + deployResult: ${{ needs.Deploy.result }} + artifactsVersion: ${{ github.event.inputs.appVersion }} PostProcess: needs: [ Initialization, Deploy ] diff --git a/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml b/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml index 56106cefb7..4db4e814d0 100644 --- a/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml +++ b/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml @@ -200,76 +200,16 @@ jobs: if: always() && (needs.Deploy.result == 'success' || needs.Deploy.result == 'failure') && startsWith(github.event.inputs.appVersion, 'PR_') runs-on: [ windows-latest ] steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Track PR Deployment - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - $prNumber = '${{ github.event.inputs.appVersion }}'.Substring(3) - $repo = '${{ github.repository }}' - $deployResult = '${{ needs.Deploy.result }}' - $state = if ($deployResult -eq 'success') { 'success' } else { 'failure' } - $headers = @{ - "Authorization" = "Bearer $env:GITHUB_TOKEN" - "Accept" = "application/vnd.github+json" - } - # Get PR branch ref - $pr = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/pulls/$prNumber" -Headers $headers - $prRef = $pr.head.ref - Write-Host "PR #$prNumber branch: $prRef" - # Get environments from Initialization - $matrixJson = '${{ needs.Initialization.outputs.environmentsMatrixJson }}' - $matrix = $matrixJson | ConvertFrom-Json - $environments = @($matrix.matrix.include | ForEach-Object { $_.environment }) - foreach ($envName in $environments) { - Write-Host "Tracking deployment for environment: $envName" - $encodedEnv = [System.Uri]::EscapeDataString($envName) - # Find the auto-created deployment (ref: main) to get its environment URL and then deactivate it - $envUrl = $null - try { - $existingDeps = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments?environment=$encodedEnv&ref=${{ github.ref_name }}&per_page=5" -Headers $headers - foreach ($dep in @($existingDeps)) { - # Retrieve environment URL from the auto-created deployment - if (-not $envUrl) { - $statuses = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$($dep.id)/statuses?per_page=1" -Headers $headers - if ($statuses -and @($statuses).Count -gt 0 -and @($statuses)[0].environment_url) { - $envUrl = @($statuses)[0].environment_url - } - } - # Deactivate the auto-created deployment so it no longer shows as active - Write-Host "Deactivating auto-created deployment $($dep.id) (ref: $($dep.ref))" - $inactiveBody = '{"state":"inactive"}' - Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$($dep.id)/statuses" -Method Post -Headers $headers -Body $inactiveBody -ContentType 'application/json' | Out-Null - } - } - catch { - Write-Host "::warning::Could not process auto-created deployment: $($_.Exception.Message)" - } - # Create deployment against the PR branch - $deployBody = @{ - ref = $prRef - environment = $envName - auto_merge = $false - required_contexts = @() - description = "Deployed via PublishToEnvironment (PR #$prNumber)" - } | ConvertTo-Json -Compress - try { - $deployment = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments" -Method Post -Headers $headers -Body $deployBody -ContentType 'application/json' - Write-Host "Created deployment $($deployment.id) against $prRef" - $statusBody = @{ - state = $state - environment = $envName - description = "Deployed PR #$prNumber to $envName" - } - if ($envUrl) { $statusBody['environment_url'] = $envUrl } - $statusJson = $statusBody | ConvertTo-Json -Compress - Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$($deployment.id)/statuses" -Method Post -Headers $headers -Body $statusJson -ContentType 'application/json' | Out-Null - Write-Host "Deployment status set to $state for $envName" - } - catch { - Write-Host "::warning::Failed to create PR deployment for environment $envName`: $($_.Exception.Message)" - } - } + uses: microsoft/AL-Go-Actions/TrackPRDeployment@main + with: + shell: powershell + environmentsMatrixJson: ${{ needs.Initialization.outputs.environmentsMatrixJson }} + deployResult: ${{ needs.Deploy.result }} + artifactsVersion: ${{ github.event.inputs.appVersion }} PostProcess: needs: [ Initialization, Deploy ] From 18395e9564cbfdb9cf82f28994a90e568f5968c4 Mon Sep 17 00:00:00 2001 From: spetersenms Date: Thu, 19 Feb 2026 16:26:31 +0100 Subject: [PATCH 06/11] Release notes --- RELEASENOTES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1f7b9dec70..a8890cecca 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,6 +1,11 @@ ### Issues - Issue 1915 CICD fails on releases/26.x branch - '26.x' cannot be recognized as a semantic version string +- Issue 2118 Deployments from "Publish To Environment" not tracked against PR branch + +### PR deployment tracking + +When deploying a PR build via "Publish To Environment", the deployment is now correctly tracked against the PR branch instead of the trigger branch (e.g. main). Previously, GitHub would show the deployment against the latest commit on main, which was misleading. A new `TrackPRDeployment` action runs after the deploy job to deactivate the auto-created deployment and create one pointing to the actual PR branch. ## v8.2 From a823b1617f72f6d3100baa580403b37672a82520 Mon Sep 17 00:00:00 2001 From: spetersenms Date: Thu, 19 Feb 2026 16:26:37 +0100 Subject: [PATCH 07/11] tests --- Tests/TrackPRDeployment.Action.Test.ps1 | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 Tests/TrackPRDeployment.Action.Test.ps1 diff --git a/Tests/TrackPRDeployment.Action.Test.ps1 b/Tests/TrackPRDeployment.Action.Test.ps1 new file mode 100644 index 0000000000..721f8023b0 --- /dev/null +++ b/Tests/TrackPRDeployment.Action.Test.ps1 @@ -0,0 +1,28 @@ +Get-Module TestActionsHelper | Remove-Module -Force +Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + +Describe "TrackPRDeployment Action Tests" { + BeforeAll { + $actionName = "TrackPRDeployment" + $scriptRoot = Join-Path $PSScriptRoot "..\Actions\$actionName" -Resolve + $scriptName = "$actionName.ps1" + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'scriptPath', Justification = 'False positive.')] + $scriptPath = Join-Path $scriptRoot $scriptName + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'actionScript', Justification = 'False positive.')] + $actionScript = GetActionScript -scriptRoot $scriptRoot -scriptName $scriptName + } + + It 'Compile Action' { + Invoke-Expression $actionScript + } + + It 'Test action.yaml matches script' { + $outputs = [ordered]@{ + } + YamlTest -scriptRoot $scriptRoot -actionName $actionName -actionScript $actionScript -outputs $outputs + } + + # Call action + +} From 821516876279f6853fecd1781b60d496f163fed1 Mon Sep 17 00:00:00 2001 From: spetersenms Date: Thu, 19 Feb 2026 16:44:26 +0100 Subject: [PATCH 08/11] pre commit --- Actions/TrackPRDeployment/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Actions/TrackPRDeployment/README.md b/Actions/TrackPRDeployment/README.md index 4210056212..87d0ec291a 100644 --- a/Actions/TrackPRDeployment/README.md +++ b/Actions/TrackPRDeployment/README.md @@ -18,7 +18,7 @@ None | token | | The GitHub token running the action | github.token | | environmentsMatrixJson | Yes | JSON string with the environments matrix from Initialization | | | deployResult | Yes | The result of the Deploy job (success or failure) | | -| artifactsVersion | Yes | Artifacts version (PR_\) | | +| artifactsVersion | Yes | Artifacts version (PR\_\) | | ## OUTPUT From 92887063d0c760fca36e3f2f5606dcbacfed3fb2 Mon Sep 17 00:00:00 2001 From: spetersenms Date: Thu, 19 Feb 2026 16:57:32 +0100 Subject: [PATCH 09/11] Removed accidental newline in deploy action --- Actions/Deploy/Deploy.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/Actions/Deploy/Deploy.ps1 b/Actions/Deploy/Deploy.ps1 index 174fed9109..7851dcd5c0 100644 --- a/Actions/Deploy/Deploy.ps1 +++ b/Actions/Deploy/Deploy.ps1 @@ -214,7 +214,6 @@ else { Write-Host "Publishing apps using automation API" Publish-PerTenantExtensionApps @parameters } - } } catch { From 50bb7dfb3b5c50b426d574f411ce517340cfc3ed Mon Sep 17 00:00:00 2001 From: spetersenms Date: Fri, 20 Feb 2026 16:22:17 +0100 Subject: [PATCH 10/11] Improved code structure and search for sha --- Actions/TrackPRDeployment/README.md | 1 + .../TrackPRDeployment/TrackPRDeployment.ps1 | 167 ++++++++++++------ Actions/TrackPRDeployment/action.yaml | 7 +- 3 files changed, 123 insertions(+), 52 deletions(-) diff --git a/Actions/TrackPRDeployment/README.md b/Actions/TrackPRDeployment/README.md index 87d0ec291a..7197d53cd3 100644 --- a/Actions/TrackPRDeployment/README.md +++ b/Actions/TrackPRDeployment/README.md @@ -19,6 +19,7 @@ None | environmentsMatrixJson | Yes | JSON string with the environments matrix from Initialization | | | deployResult | Yes | The result of the Deploy job (success or failure) | | | artifactsVersion | Yes | Artifacts version (PR\_\) | | +| sha | | The commit SHA of the workflow run, used to identify the correct auto-created deployment | github.sha | ## OUTPUT diff --git a/Actions/TrackPRDeployment/TrackPRDeployment.ps1 b/Actions/TrackPRDeployment/TrackPRDeployment.ps1 index c3c0f4167f..f658c1b826 100644 --- a/Actions/TrackPRDeployment/TrackPRDeployment.ps1 +++ b/Actions/TrackPRDeployment/TrackPRDeployment.ps1 @@ -6,83 +6,148 @@ Param( [Parameter(Mandatory = $true)] [string] $deployResult, [Parameter(Mandatory = $true)] - [string] $artifactsVersion + [string] $artifactsVersion, + [Parameter(Mandatory = $false)] + [string] $sha = $ENV:GITHUB_SHA ) -$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) + +<# + .SYNOPSIS + Deactivate auto-created deployments and retrieve the environment URL. + .DESCRIPTION + The environment: key on the Deploy job auto-creates a deployment against the trigger ref (e.g. main). + This function finds those deployments, retrieves the environment URL from the latest status, and + deactivates them so they no longer show as active. +#> +function DeactivateAutoDeployments { + Param( + [hashtable] $headers, + [string] $repository, + [string] $environmentName, + [string] $triggerRef, + [string] $sha + ) + + $apiBase = "https://api.github.com/repos/$repository" + $encodedEnv = [System.Uri]::EscapeDataString($environmentName) + $envUrl = $null + + $listUri = "$apiBase/deployments?environment=$encodedEnv&ref=$triggerRef&sha=$sha&per_page=1" + OutputDebug "GET $listUri" + $existingDeps = (InvokeWebRequest -Headers $headers -Uri $listUri).Content | ConvertFrom-Json + if ($existingDeps -and @($existingDeps).Count -gt 0) { + $dep = @($existingDeps)[0] + OutputDebug "Found auto-created deployment $($dep.id) (ref: $($dep.ref), sha: $($dep.sha))" + $statusesUri = "$apiBase/deployments/$($dep.id)/statuses?per_page=1" + OutputDebug "GET $statusesUri" + $statuses = (InvokeWebRequest -Headers $headers -Uri $statusesUri).Content | ConvertFrom-Json + if ($statuses -and @($statuses).Count -gt 0) { + OutputDebug "Latest status: state=$(@($statuses)[0].state), environment_url=$(@($statuses)[0].environment_url)" + if (@($statuses)[0].environment_url) { + $envUrl = @($statuses)[0].environment_url + } + if (@($statuses)[0].state -ne 'inactive') { + Write-Host "Deactivating auto-created deployment $($dep.id) (ref: $($dep.ref))" + $deactivateUri = "$apiBase/deployments/$($dep.id)/statuses" + OutputDebug "POST $deactivateUri (state: inactive)" + InvokeWebRequest -Headers $headers -Method 'POST' -Uri $deactivateUri -Body '{"state":"inactive"}' | Out-Null + } + else { + Write-Host "Auto-created deployment $($dep.id) is already inactive, skipping" + } + } + } + else { + OutputDebug "No auto-created deployment found for environment=$environmentName, ref=$triggerRef, sha=$sha" + } + + return $envUrl +} + +<# + .SYNOPSIS + Create a deployment record against the PR branch and set its status. +#> +function CreatePRDeployment { + Param( + [hashtable] $headers, + [string] $repository, + [string] $prRef, + [string] $prNumber, + [string] $environmentName, + [string] $environmentUrl, + [string] $state + ) + + $apiBase = "https://api.github.com/repos/$repository" + + $deployBody = @{ + ref = $prRef + environment = $environmentName + auto_merge = $false + required_contexts = @() + description = "Deployed via PublishToEnvironment (PR #$prNumber)" + } | ConvertTo-Json -Compress + + $createUri = "$apiBase/deployments" + OutputDebug "POST $createUri (ref: $prRef, environment: $environmentName)" + $deployment = (InvokeWebRequest -Headers $headers -Method 'POST' -Uri $createUri -Body $deployBody).Content | ConvertFrom-Json + Write-Host "Created deployment $($deployment.id) against $prRef" + + $statusBody = @{ + state = $state + environment = $environmentName + description = "Deployed PR #$prNumber to $environmentName" + } + if ($environmentUrl) { $statusBody['environment_url'] = $environmentUrl } + $statusJson = $statusBody | ConvertTo-Json -Compress + + $statusUri = "$apiBase/deployments/$($deployment.id)/statuses" + OutputDebug "POST $statusUri (state: $state)" + InvokeWebRequest -Headers $headers -Method 'POST' -Uri $statusUri -Body $statusJson | Out-Null + Write-Host "Deployment status set to $state for $environmentName" +} -# Extract PR number from artifactsVersion (format: PR_) +# Main $prNumber = $artifactsVersion.Substring(3) $repo = $ENV:GITHUB_REPOSITORY $triggerRef = $ENV:GITHUB_REF_NAME $state = if ($deployResult -eq 'success') { 'success' } else { 'failure' } -$headers = @{ - "Authorization" = "Bearer $token" - "Accept" = "application/vnd.github+json" -} +OutputDebug "PR number: $prNumber, repository: $repo, triggerRef: $triggerRef, sha: $sha, deployResult: $deployResult" + +$headers = GetHeaders -token $token -# Get PR branch ref -Write-Host "Resolving PR #$prNumber branch ref" -$pr = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/pulls/$prNumber" -Headers $headers -$prRef = $pr.head.ref +# Get PR branch ref using existing helper from Deploy.psm1 +Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "..\Deploy\Deploy.psm1" -Resolve) +$prRef = GetHeadRefFromPRId -repository $repo -prId $prNumber -token $token +if (-not $prRef) { + throw "Could not determine PR branch for PR #$prNumber" +} Write-Host "PR #$prNumber branch: $prRef" # Parse environments from the matrix JSON $matrix = $environmentsMatrixJson | ConvertFrom-Json $environments = @($matrix.matrix.include | ForEach-Object { $_.environment }) +OutputDebug "Environments to process: $($environments -join ', ')" foreach ($envName in $environments) { Write-Host "Tracking deployment for environment: $envName" - $encodedEnv = [System.Uri]::EscapeDataString($envName) - # Find the auto-created deployment (ref: trigger branch) to get its environment URL and then deactivate it $envUrl = $null try { - $existingDeps = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments?environment=$encodedEnv&ref=$triggerRef&per_page=5" -Headers $headers - foreach ($dep in @($existingDeps)) { - # Retrieve environment URL from the auto-created deployment - if (-not $envUrl) { - $statuses = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$($dep.id)/statuses?per_page=1" -Headers $headers - if ($statuses -and @($statuses).Count -gt 0 -and @($statuses)[0].environment_url) { - $envUrl = @($statuses)[0].environment_url - } - } - # Deactivate the auto-created deployment so it no longer shows as active - Write-Host "Deactivating auto-created deployment $($dep.id) (ref: $($dep.ref))" - $inactiveBody = '{"state":"inactive"}' - Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$($dep.id)/statuses" -Method Post -Headers $headers -Body $inactiveBody -ContentType 'application/json' | Out-Null - } + $envUrl = DeactivateAutoDeployments -headers $headers -repository $repo -environmentName $envName -triggerRef $triggerRef -sha $sha } catch { - Write-Host "::warning::Could not process auto-created deployment: $($_.Exception.Message)" + OutputWarning -message "Could not deactivate auto-created deployment for $envName`: $($_.Exception.Message)" } - # Create deployment against the PR branch - $deployBody = @{ - ref = $prRef - environment = $envName - auto_merge = $false - required_contexts = @() - description = "Deployed via PublishToEnvironment (PR #$prNumber)" - } | ConvertTo-Json -Compress - try { - $deployment = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments" -Method Post -Headers $headers -Body $deployBody -ContentType 'application/json' - Write-Host "Created deployment $($deployment.id) against $prRef" - - $statusBody = @{ - state = $state - environment = $envName - description = "Deployed PR #$prNumber to $envName" - } - if ($envUrl) { $statusBody['environment_url'] = $envUrl } - $statusJson = $statusBody | ConvertTo-Json -Compress - - Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/deployments/$($deployment.id)/statuses" -Method Post -Headers $headers -Body $statusJson -ContentType 'application/json' | Out-Null - Write-Host "Deployment status set to $state for $envName" + CreatePRDeployment -headers $headers -repository $repo -prRef $prRef -prNumber $prNumber -environmentName $envName -environmentUrl $envUrl -state $state } catch { - Write-Host "::warning::Failed to create PR deployment for environment $envName`: $($_.Exception.Message)" + OutputWarning -message "Failed to create PR deployment for $envName`: $($_.Exception.Message)" } } diff --git a/Actions/TrackPRDeployment/action.yaml b/Actions/TrackPRDeployment/action.yaml index 1884b62983..6f5f009edb 100644 --- a/Actions/TrackPRDeployment/action.yaml +++ b/Actions/TrackPRDeployment/action.yaml @@ -18,6 +18,10 @@ inputs: artifactsVersion: description: Artifacts version (PR_) required: true + sha: + description: The commit SHA of the workflow run, used to identify the correct auto-created deployment + required: false + default: ${{ github.sha }} runs: using: composite steps: @@ -28,9 +32,10 @@ runs: _environmentsMatrixJson: ${{ inputs.environmentsMatrixJson }} _deployResult: ${{ inputs.deployResult }} _artifactsVersion: ${{ inputs.artifactsVersion }} + _sha: ${{ inputs.sha }} run: | ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "TrackPRDeployment" -Action { - ${{ github.action_path }}/TrackPRDeployment.ps1 -token $ENV:_token -environmentsMatrixJson $ENV:_environmentsMatrixJson -deployResult $ENV:_deployResult -artifactsVersion $ENV:_artifactsVersion + ${{ github.action_path }}/TrackPRDeployment.ps1 -token $ENV:_token -environmentsMatrixJson $ENV:_environmentsMatrixJson -deployResult $ENV:_deployResult -artifactsVersion $ENV:_artifactsVersion -sha $ENV:_sha } branding: icon: terminal From 496ae1736eedfe8e083ef47f1e080e8216748023 Mon Sep 17 00:00:00 2001 From: spetersenms Date: Mon, 23 Feb 2026 11:12:19 +0100 Subject: [PATCH 11/11] Using log url --- .../TrackPRDeployment/TrackPRDeployment.ps1 | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Actions/TrackPRDeployment/TrackPRDeployment.ps1 b/Actions/TrackPRDeployment/TrackPRDeployment.ps1 index f658c1b826..428d6b6955 100644 --- a/Actions/TrackPRDeployment/TrackPRDeployment.ps1 +++ b/Actions/TrackPRDeployment/TrackPRDeployment.ps1 @@ -34,6 +34,8 @@ function DeactivateAutoDeployments { $encodedEnv = [System.Uri]::EscapeDataString($environmentName) $envUrl = $null + $logUrl = $null + $listUri = "$apiBase/deployments?environment=$encodedEnv&ref=$triggerRef&sha=$sha&per_page=1" OutputDebug "GET $listUri" $existingDeps = (InvokeWebRequest -Headers $headers -Uri $listUri).Content | ConvertFrom-Json @@ -44,10 +46,13 @@ function DeactivateAutoDeployments { OutputDebug "GET $statusesUri" $statuses = (InvokeWebRequest -Headers $headers -Uri $statusesUri).Content | ConvertFrom-Json if ($statuses -and @($statuses).Count -gt 0) { - OutputDebug "Latest status: state=$(@($statuses)[0].state), environment_url=$(@($statuses)[0].environment_url)" + OutputDebug "Latest status: state=$(@($statuses)[0].state), environment_url=$(@($statuses)[0].environment_url), log_url=$(@($statuses)[0].log_url)" if (@($statuses)[0].environment_url) { $envUrl = @($statuses)[0].environment_url } + if (@($statuses)[0].log_url) { + $logUrl = @($statuses)[0].log_url + } if (@($statuses)[0].state -ne 'inactive') { Write-Host "Deactivating auto-created deployment $($dep.id) (ref: $($dep.ref))" $deactivateUri = "$apiBase/deployments/$($dep.id)/statuses" @@ -63,7 +68,10 @@ function DeactivateAutoDeployments { OutputDebug "No auto-created deployment found for environment=$environmentName, ref=$triggerRef, sha=$sha" } - return $envUrl + return @{ + EnvironmentUrl = $envUrl + LogUrl = $logUrl + } } <# @@ -78,6 +86,7 @@ function CreatePRDeployment { [string] $prNumber, [string] $environmentName, [string] $environmentUrl, + [string] $logUrl, [string] $state ) @@ -102,6 +111,7 @@ function CreatePRDeployment { description = "Deployed PR #$prNumber to $environmentName" } if ($environmentUrl) { $statusBody['environment_url'] = $environmentUrl } + if ($logUrl) { $statusBody['log_url'] = $logUrl } $statusJson = $statusBody | ConvertTo-Json -Compress $statusUri = "$apiBase/deployments/$($deployment.id)/statuses" @@ -136,16 +146,16 @@ OutputDebug "Environments to process: $($environments -join ', ')" foreach ($envName in $environments) { Write-Host "Tracking deployment for environment: $envName" - $envUrl = $null + $deploymentInfo = $null try { - $envUrl = DeactivateAutoDeployments -headers $headers -repository $repo -environmentName $envName -triggerRef $triggerRef -sha $sha + $deploymentInfo = DeactivateAutoDeployments -headers $headers -repository $repo -environmentName $envName -triggerRef $triggerRef -sha $sha } catch { OutputWarning -message "Could not deactivate auto-created deployment for $envName`: $($_.Exception.Message)" } try { - CreatePRDeployment -headers $headers -repository $repo -prRef $prRef -prNumber $prNumber -environmentName $envName -environmentUrl $envUrl -state $state + CreatePRDeployment -headers $headers -repository $repo -prRef $prRef -prNumber $prNumber -environmentName $envName -environmentUrl $deploymentInfo.EnvironmentUrl -logUrl $deploymentInfo.LogUrl -state $state } catch { OutputWarning -message "Failed to create PR deployment for $envName`: $($_.Exception.Message)"