From 1579c7c0295810a36db68f3ff9d087aec8cc59d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 14:13:37 -0700 Subject: [PATCH 01/11] [release/8.0]: Bump Microsoft.AspNetCore.Identity.EntityFrameworkCore from 8.0.30 to 8.0.31 (#38998) --- .../EFCore.AspNet.Specification.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/EFCore.AspNet.Specification.Tests/EFCore.AspNet.Specification.Tests.csproj b/test/EFCore.AspNet.Specification.Tests/EFCore.AspNet.Specification.Tests.csproj index 3cf7e13df88..e15f5414aa0 100644 --- a/test/EFCore.AspNet.Specification.Tests/EFCore.AspNet.Specification.Tests.csproj +++ b/test/EFCore.AspNet.Specification.Tests/EFCore.AspNet.Specification.Tests.csproj @@ -48,7 +48,7 @@ - + From 9b205370ad692b46c5a69969e7651dd134192def Mon Sep 17 00:00:00 2001 From: "dotnet-release-bot[bot]" <280112824+dotnet-release-bot[bot]@users.noreply.github.com> Date: Wed, 7 Oct 2026 11:47:19 -0700 Subject: [PATCH 02/11] Update branding on release/9.0 (#39184) Co-authored-by: dotnet-release-bot[bot] <280112824+dotnet-release-bot[bot]@users.noreply.github.com> --- eng/Versions.props | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index 0702caa3db6..6142047222f 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -1,9 +1,8 @@ - 9.0.21 + 9.0.22 servicing - - + False true - + From 63ab07988ad160397f11f07655a913e3b0dbe620 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Oct 2026 16:00:20 -0700 Subject: [PATCH 05/11] [release/9.0]: Bump Grpc.AspNetCore from 2.83.0 to 2.84.0 (#39132) --- updated-dependencies: - dependency-name: Grpc.AspNetCore dependency-version: 2.84.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- test/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Directory.Packages.props b/test/Directory.Packages.props index 6c0147cda28..2fe3e18fc38 100644 --- a/test/Directory.Packages.props +++ b/test/Directory.Packages.props @@ -14,7 +14,7 @@ - + From 7b7f63d004e8c9a3ae4d8434b7f6e2e9d5e4397b Mon Sep 17 00:00:00 2001 From: William Godbe Date: Wed, 7 Oct 2026 16:00:56 -0700 Subject: [PATCH 06/11] [release/9.0] Avoid artifact name collisions on job retries (#39051) * Avoid artifact collisions on job retries Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Allow test result artifact upload failures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Suffix custom log artifact names on retries Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Will Godbe Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- azure-pipelines-public.yml | 6 +++--- azure-pipelines.yml | 9 ++++++--- eng/common/templates-official/job/job.yml | 2 +- eng/common/templates/job/job.yml | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/azure-pipelines-public.yml b/azure-pipelines-public.yml index a58d865a95a..d80a2832bd8 100644 --- a/azure-pipelines-public.yml +++ b/azure-pipelines-public.yml @@ -86,7 +86,7 @@ stages: continueOnError: true inputs: pathtoPublish: artifacts/TestResults/$(_BuildConfig)/ - artifactName: $(Agent.Os)_$(Agent.JobName) TestResults + artifactName: $(Agent.Os)_$(Agent.JobName) TestResults_Attempt_$(System.JobAttempt) artifactType: Container parallel: true @@ -110,7 +110,7 @@ stages: continueOnError: true inputs: pathtoPublish: artifacts/TestResults/$(_BuildConfig)/ - artifactName: $(Agent.Os)_$(Agent.JobName) TestResults + artifactName: $(Agent.Os)_$(Agent.JobName) TestResults_Attempt_$(System.JobAttempt) artifactType: Container parallel: true @@ -135,7 +135,7 @@ stages: continueOnError: true inputs: pathtoPublish: artifacts/TestResults/$(_BuildConfig)/ - artifactName: $(Agent.Os)_$(Agent.JobName) TestResults + artifactName: $(Agent.Os)_$(Agent.JobName) TestResults_Attempt_$(System.JobAttempt) artifactType: Container parallel: true diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4b38f0ad63d..dfc835cfd93 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -139,8 +139,9 @@ extends: - output: pipelineArtifact displayName: Upload TestResults condition: always() + continueOnError: true path: artifacts/TestResults/$(_BuildConfig)/ - artifact: $(Agent.Os)_$(Agent.JobName) TestResults + artifact: $(Agent.Os)_$(Agent.JobName) TestResults_Attempt_$(System.JobAttempt) - job: macOS pool: name: Azure Pipelines @@ -166,8 +167,9 @@ extends: - output: pipelineArtifact displayName: Upload TestResults condition: always() + continueOnError: true path: artifacts/TestResults/$(_BuildConfig)/ - artifact: $(Agent.Os)_$(Agent.JobName) TestResults + artifact: $(Agent.Os)_$(Agent.JobName) TestResults_Attempt_$(System.JobAttempt) - job: Linux timeoutInMinutes: 180 pool: @@ -191,8 +193,9 @@ extends: - output: pipelineArtifact displayName: Upload TestResults condition: always() + continueOnError: true path: artifacts/TestResults/$(_BuildConfig)/ - artifact: $(Agent.Os)_$(Agent.JobName) TestResults + artifact: $(Agent.Os)_$(Agent.JobName) TestResults_Attempt_$(System.JobAttempt) - job: Helix timeoutInMinutes: 180 pool: diff --git a/eng/common/templates-official/job/job.yml b/eng/common/templates-official/job/job.yml index 81ea7a261f2..329dcc4b34a 100644 --- a/eng/common/templates-official/job/job.yml +++ b/eng/common/templates-official/job/job.yml @@ -46,7 +46,7 @@ jobs: displayName: Publish Logs PathtoPublish: '$(Build.ArtifactStagingDirectory)/artifacts/log/$(_BuildConfig)' publishLocation: Container - ArtifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)' ) }} + ArtifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)') }}_Attempt_$(System.JobAttempt) continueOnError: true condition: always() sbomEnabled: false # we don't need SBOM for logs diff --git a/eng/common/templates/job/job.yml b/eng/common/templates/job/job.yml index 5bdd3dd85fd..d5a4ae6b446 100644 --- a/eng/common/templates/job/job.yml +++ b/eng/common/templates/job/job.yml @@ -66,7 +66,7 @@ jobs: displayName: Publish Logs pathToPublish: '$(Build.ArtifactStagingDirectory)/artifacts/log/$(_BuildConfig)' publishLocation: Container - artifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)' ) }} + artifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)') }}_Attempt_$(System.JobAttempt) continueOnError: true condition: always() From b36228c49620a32fbb72b30781e8063d1055fcbb Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2026 23:53:31 +0000 Subject: [PATCH 07/11] Update dependencies from build 335710 (#39204) [release/10.0] Source code updates from dotnet/dotnet --- NuGet.config | 2 +- eng/Version.Details.props | 10 +- eng/Version.Details.xml | 48 ++-- eng/common/Get-GitHubAppToken.ps1 | 123 +++++--- .../core-templates/job/helix-job-monitor.yml | 128 +++------ eng/common/core-templates/job/onelocbuild.yml | 8 +- .../steps/get-github-app-token.yml | 29 +- .../core-templates/steps/send-to-helix.yml | 264 +++++++++++------- eng/common/run-helix-job-monitor.sh | 65 +++++ global.json | 4 +- 10 files changed, 400 insertions(+), 281 deletions(-) create mode 100644 eng/common/run-helix-job-monitor.sh diff --git a/NuGet.config b/NuGet.config index 84597d1f8b2..db0ad66ff68 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,7 +4,7 @@ - + diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 61fb7d68d54..a1a16f27d70 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -6,9 +6,9 @@ This file should be imported by eng/Versions.props - 10.0.0-beta.26505.113 - 10.0.0-beta.26505.113 - 10.0.0-beta.26505.113 + 10.0.0-beta.26508.104 + 10.0.0-beta.26508.104 + 10.0.0-beta.26508.104 10.0.14 10.0.14 10.0.14 @@ -16,10 +16,10 @@ This file should be imported by eng/Versions.props 10.0.14 10.0.14 10.0.14 - 10.0.14-servicing.26505.113 + 10.0.14-servicing.26508.104 10.0.14 10.0.14 - 10.0.14-servicing.26505.113 + 10.0.14-servicing.26508.104 10.0.14 10.0.14 10.0.14 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 6812cd834a5..7f297dcb82b 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,80 +1,80 @@ - + https://github.com/dotnet/dotnet - c4287c01fc8bb2c4ce9320bca4d61ec945a7493d + 8d12953c7e56fbc03f5b22372c0013d69c908517 https://github.com/dotnet/dotnet - c4287c01fc8bb2c4ce9320bca4d61ec945a7493d + 8d12953c7e56fbc03f5b22372c0013d69c908517 https://github.com/dotnet/dotnet - c4287c01fc8bb2c4ce9320bca4d61ec945a7493d + 8d12953c7e56fbc03f5b22372c0013d69c908517 https://github.com/dotnet/dotnet - c4287c01fc8bb2c4ce9320bca4d61ec945a7493d + 8d12953c7e56fbc03f5b22372c0013d69c908517 https://github.com/dotnet/dotnet - c4287c01fc8bb2c4ce9320bca4d61ec945a7493d + 8d12953c7e56fbc03f5b22372c0013d69c908517 https://github.com/dotnet/dotnet - c4287c01fc8bb2c4ce9320bca4d61ec945a7493d + 8d12953c7e56fbc03f5b22372c0013d69c908517 https://github.com/dotnet/dotnet - c4287c01fc8bb2c4ce9320bca4d61ec945a7493d + 8d12953c7e56fbc03f5b22372c0013d69c908517 - + https://github.com/dotnet/dotnet - c4287c01fc8bb2c4ce9320bca4d61ec945a7493d + 8d12953c7e56fbc03f5b22372c0013d69c908517 https://github.com/dotnet/dotnet - c4287c01fc8bb2c4ce9320bca4d61ec945a7493d + 8d12953c7e56fbc03f5b22372c0013d69c908517 https://github.com/dotnet/dotnet - c4287c01fc8bb2c4ce9320bca4d61ec945a7493d + 8d12953c7e56fbc03f5b22372c0013d69c908517 - + https://github.com/dotnet/dotnet - c4287c01fc8bb2c4ce9320bca4d61ec945a7493d + 8d12953c7e56fbc03f5b22372c0013d69c908517 https://github.com/dotnet/dotnet - c4287c01fc8bb2c4ce9320bca4d61ec945a7493d + 8d12953c7e56fbc03f5b22372c0013d69c908517 https://github.com/dotnet/dotnet - c4287c01fc8bb2c4ce9320bca4d61ec945a7493d + 8d12953c7e56fbc03f5b22372c0013d69c908517 https://github.com/dotnet/dotnet - c4287c01fc8bb2c4ce9320bca4d61ec945a7493d + 8d12953c7e56fbc03f5b22372c0013d69c908517 https://github.com/dotnet/dotnet - c4287c01fc8bb2c4ce9320bca4d61ec945a7493d + 8d12953c7e56fbc03f5b22372c0013d69c908517 - + https://github.com/dotnet/dotnet - c4287c01fc8bb2c4ce9320bca4d61ec945a7493d + 8d12953c7e56fbc03f5b22372c0013d69c908517 - + https://github.com/dotnet/dotnet - c4287c01fc8bb2c4ce9320bca4d61ec945a7493d + 8d12953c7e56fbc03f5b22372c0013d69c908517 - + https://github.com/dotnet/dotnet - c4287c01fc8bb2c4ce9320bca4d61ec945a7493d + 8d12953c7e56fbc03f5b22372c0013d69c908517 diff --git a/eng/common/Get-GitHubAppToken.ps1 b/eng/common/Get-GitHubAppToken.ps1 index ea776bd6bc2..ec005e487c3 100644 --- a/eng/common/Get-GitHubAppToken.ps1 +++ b/eng/common/Get-GitHubAppToken.ps1 @@ -1,13 +1,11 @@ # Mints a short-lived GitHub App installation access token by signing a JWT -# with a private key stored in Azure Key Vault (RSA, RS256). The signed JWT is -# exchanged with the GitHub API for a token scoped to a single installation. +# with an RSA private key (RS256). The signed JWT is exchanged with the GitHub +# API for a token scoped to a single installation. # # Requirements: -# - A GitHub App whose private key has been uploaded into Key Vault as an RSA -# key (the PEM converted to a Key Vault *key*, NOT stored as a secret). -# - The caller (the federated Azure service connection used to run this script) -# must have the `Key Vault Crypto User` role (or at minimum the `Sign` -# action) on that key. +# - A GitHub App ID and PEM private key stored as Azure Key Vault secrets. +# - The federated Azure service connection running this script must have +# `Get` access to those two secrets. # - The App must be installed on the target organization/account # (`InstallationOwner`) with the permissions/repositories it needs. # @@ -16,17 +14,17 @@ [CmdletBinding()] param( - # Name of the Key Vault that holds the GitHub App's RSA signing key. + # Name of the Key Vault holding the GitHub App credentials. [Parameter(Mandatory = $true)] [string] $KeyVaultName, - # Name of the RSA key inside the Key Vault (the App's private key). + # Secret Manager projection containing the GitHub App ID. [Parameter(Mandatory = $true)] - [string] $KeyName, + [string] $AppIdSecretName, - # The GitHub App's Client ID (the value to put in the `iss` JWT claim). + # Secret Manager projection containing the PEM private key. [Parameter(Mandatory = $true)] - [string] $AppClientId, + [string] $AppPrivateKeySecretName, # Login of the organization or user account whose installation we should # mint the token for (e.g. `dotnet`, `microsoft`). @@ -39,16 +37,69 @@ param( [Parameter(Mandatory = $false)] [string] $OutputVariableName ) - $ErrorActionPreference = 'Stop' $PSNativeCommandUseErrorActionPreference = $true . $PSScriptRoot\pipeline-logging-functions.ps1 +if ($KeyVaultName -notmatch '^[A-Za-z][A-Za-z0-9-]{1,22}[A-Za-z0-9]$' -or $KeyVaultName.Contains('--')) { + Write-PipelineTelemetryError -Category 'Build' -Message "KeyVaultName '$KeyVaultName' is not a valid Azure Key Vault name." + exit 1 +} + function ConvertTo-Base64Url([byte[]] $bytes) { return [Convert]::ToBase64String($bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_') } +$previousNativeCommandErrorPreference = $PSNativeCommandUseErrorActionPreference +try { + # Azure CLI can emit non-fatal Python warnings to stderr. + $PSNativeCommandUseErrorActionPreference = $false + $keyVaultAccessToken = az account get-access-token ` + --resource https://vault.azure.net ` + --query accessToken ` + --output tsv ` + --only-show-errors + $tokenExitCode = $LASTEXITCODE +} +catch { + Write-PipelineTelemetryError -Category 'Build' -Message "Failed to acquire an Azure Key Vault access token: $_" + exit 1 +} +finally { + $PSNativeCommandUseErrorActionPreference = $previousNativeCommandErrorPreference +} +if ($tokenExitCode -ne 0 -or [string]::IsNullOrWhiteSpace($keyVaultAccessToken)) { + Write-PipelineTelemetryError -Category 'Build' -Message "'az account get-access-token' exited with code $tokenExitCode while acquiring an Azure Key Vault access token." + exit 1 +} + +function Get-KeyVaultSecret([string] $SecretName) { + # Use the data-plane REST API because `az keyvault secret show` can fail + # with Errno 22 on hosted Windows agents when reading these projections. + $escapedSecretName = [Uri]::EscapeDataString($SecretName) + $secretUri = "https://$KeyVaultName.vault.azure.net/secrets/$escapedSecretName`?api-version=7.4" + try { + $response = Invoke-RestMethod ` + -Uri $secretUri ` + -Headers @{ Authorization = "Bearer $keyVaultAccessToken" } ` + -Method Get + } + catch { + Write-PipelineTelemetryError -Category 'Build' -Message "Failed to read secret '$SecretName' from vault '$KeyVaultName': $_. Verify the secret exists and the service connection has 'Key Vault Secrets User' access to it." + exit 1 + } + if ([string]::IsNullOrWhiteSpace($response.value)) { + Write-PipelineTelemetryError -Category 'Build' -Message "Secret '$SecretName' in vault '$KeyVaultName' is empty." + exit 1 + } + return [string] $response.value +} + +Write-Host "Reading GitHub App credentials from vault '$KeyVaultName'..." +$appId = Get-KeyVaultSecret $AppIdSecretName +$privateKey = Get-KeyVaultSecret $AppPrivateKeySecretName + # Build JWT header and payload. Use [ordered] hashtables so JSON # serialization is deterministic. $jwtHeader = [ordered]@{ @@ -59,46 +110,38 @@ $now = [System.DateTimeOffset]::UtcNow $jwtPayload = [ordered]@{ iat = $now.AddMinutes(-1).ToUnixTimeSeconds() exp = $now.AddMinutes(5).ToUnixTimeSeconds() - iss = $AppClientId + iss = $appId } $headerEncoded = ConvertTo-Base64Url ([System.Text.Encoding]::UTF8.GetBytes(($jwtHeader | ConvertTo-Json -Compress))) $payloadEncoded = ConvertTo-Base64Url ([System.Text.Encoding]::UTF8.GetBytes(($jwtPayload | ConvertTo-Json -Compress))) $signingInput = "$headerEncoded.$payloadEncoded" -# Key Vault `sign` expects the *digest* (base64), not the raw bytes. -$sha256 = [System.Security.Cryptography.SHA256]::Create() -$digestBytes = $sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($signingInput)) -$digestBase64 = [Convert]::ToBase64String($digestBytes) +$sha256 = [System.Security.Cryptography.SHA256]::Create() +try { + $digestBytes = $sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($signingInput)) +} +finally { + $sha256.Dispose() +} -Write-Host "Signing JWT with key '$KeyName' in vault '$KeyVaultName'..." -$previousNativeCommandErrorPreference = $PSNativeCommandUseErrorActionPreference +Write-Host 'Signing JWT with the GitHub App private key...' +$rsa = [System.Security.Cryptography.RSA]::Create() try { - # Azure CLI can emit non-fatal Python warnings to stderr even when signing succeeds. - # Use the exit code to determine success for this invocation. - $PSNativeCommandUseErrorActionPreference = $false - $signatureBase64 = az keyvault key sign ` - --vault-name $KeyVaultName ` - --name $KeyName ` - --algorithm RS256 ` - --digest $digestBase64 ` - --query signature ` - --output tsv ` - --only-show-errors - $signExitCode = $LASTEXITCODE + $rsa.ImportFromPem($privateKey) + $signatureBytes = $rsa.SignHash( + $digestBytes, + [System.Security.Cryptography.HashAlgorithmName]::SHA256, + [System.Security.Cryptography.RSASignaturePadding]::Pkcs1) + $signatureUrl = ConvertTo-Base64Url $signatureBytes } catch { - Write-PipelineTelemetryError -Category 'Build' -Message "Failed to sign the JWT via Key Vault (key '$KeyName', vault '$KeyVaultName'): $_. Verify the service connection identity has the 'Key Vault Crypto User' role (Sign action) on the key." + Write-PipelineTelemetryError -Category 'Build' -Message "Failed to sign the GitHub App JWT with the supplied private key: $_" exit 1 } finally { - $PSNativeCommandUseErrorActionPreference = $previousNativeCommandErrorPreference -} -if ($signExitCode -ne 0 -or [string]::IsNullOrWhiteSpace($signatureBase64)) { - Write-PipelineTelemetryError -Category 'Build' -Message "'az keyvault key sign' exited with code $signExitCode for key '$KeyName' in vault '$KeyVaultName'. Verify the service connection identity has the 'Key Vault Crypto User' role (Sign action) on the key." - exit 1 + $rsa.Dispose() } -$signatureUrl = $signatureBase64.Trim().TrimEnd('=').Replace('+', '-').Replace('/', '_') $jwt = "$signingInput.$signatureUrl" $headers = @{ @@ -126,7 +169,7 @@ try { } while ($pageInstallationCount -eq 100) } catch { - Write-PipelineTelemetryError -Category 'Build' -Message "Failed to list GitHub App installations: $_. The signed JWT may be invalid or the App's Client ID ('$AppClientId') may be incorrect." + Write-PipelineTelemetryError -Category 'Build' -Message "Failed to list GitHub App installations: $_. The signed JWT may be invalid or the App ID may be incorrect." exit 1 } $matchingInstallations = @($installations | Where-Object { $_.account.login -ieq $InstallationOwner }) diff --git a/eng/common/core-templates/job/helix-job-monitor.yml b/eng/common/core-templates/job/helix-job-monitor.yml index c95e3a48cdd..a3dd1ab5341 100644 --- a/eng/common/core-templates/job/helix-job-monitor.yml +++ b/eng/common/core-templates/job/helix-job-monitor.yml @@ -69,7 +69,7 @@ parameters: type: string default: '' -# Azure service connection ID authorized for Helix. Required when +# Workload identity federation service connection name authorized for Helix. Required when # useEntraAuthentication is true. - name: azureSubscription type: string @@ -141,6 +141,21 @@ jobs: displayName: Monitor Helix Jobs timeoutInMinutes: ${{ parameters.timeoutInMinutes }} continueOnError: ${{ parameters.continueOnError }} + variables: + HELIX_MONITOR_BASE_URI: ${{ parameters.helixBaseUri }} + HELIX_MONITOR_USE_ENTRA_AUTHENTICATION: ${{ parameters.useEntraAuthentication }} + HELIX_MONITOR_ENTRA_SCOPE: ${{ parameters.helixEntraScope }} + HELIX_MONITOR_POLLING_INTERVAL_SECONDS: ${{ parameters.pollingIntervalSeconds }} + HELIX_MONITOR_FAIL_ON_FAILED_TESTS: ${{ parameters.failWorkItemsWithFailedTests }} + HELIX_MONITOR_ALLOW_NO_HELIX_JOBS: ${{ parameters.allowNoHelixJobs }} + HELIX_MONITOR_USE_FULLY_QUALIFIED_TEST_NAME: ${{ parameters.useFullyQualifiedTestName }} + HELIX_MONITOR_TIMEOUT_IN_MINUTES: ${{ parameters.timeoutInMinutes }} + HELIX_MONITOR_TEST_RESULT_UPLOAD_PARALLELISM: ${{ parameters.testResultUploadParallelism }} + HELIX_MONITOR_ORGANIZATION: ${{ parameters.organization }} + HELIX_MONITOR_REPOSITORY: ${{ parameters.repository }} + HELIX_MONITOR_TEST_RESULT_ATTACHMENT_MODE: ${{ parameters.testResultAttachmentMode }} + HELIX_MONITOR_TOOL_NUPKG_ARTIFACT_NAME: ${{ parameters.toolNupkgArtifactName }} + HELIX_MONITOR_TOOL_COMMAND: ${{ parameters.toolCommand }} ${{ if ne(length(parameters.dependsOn), 0) }}: dependsOn: ${{ parameters.dependsOn }} ${{ if ne(parameters.condition, '') }}: @@ -162,22 +177,6 @@ jobs: - pwsh: throw "azureSubscription must be set when useEntraAuthentication is true." displayName: Validate Helix Entra authentication - - ${{ if eq(parameters.useEntraAuthentication, true) }}: - - task: AzureCLI@2 - displayName: Initialize Helix Entra authentication - inputs: - azureSubscription: ${{ parameters.azureSubscription }} - addSpnToEnvironment: true - scriptType: pscore - scriptLocation: inlineScript - inlineScript: | - if ([string]::IsNullOrWhiteSpace($env:servicePrincipalId) -or [string]::IsNullOrWhiteSpace($env:tenantId)) { - throw "The Helix Azure service connection did not provide a service principal or tenant ID." - } - - Write-Host "##vso[task.setvariable variable=HelixEntraClientId]$env:servicePrincipalId" - Write-Host "##vso[task.setvariable variable=HelixEntraTenantId]$env:tenantId" - - ${{ if ne(parameters.toolNupkgArtifactName, '') }}: - task: DownloadPipelineArtifact@2 displayName: Download Helix Job Monitor artifact @@ -246,77 +245,26 @@ jobs: - bash: ./eng/common/dotnet.sh tool restore displayName: Restore Helix Job Monitor - - bash: | - set -euo pipefail - - toolArgs=( - --helix-base-uri '${{ parameters.helixBaseUri }}' - --use-entra-authentication '${{ parameters.useEntraAuthentication }}' - --helix-entra-scope '${{ parameters.helixEntraScope }}' - --polling-interval-seconds '${{ parameters.pollingIntervalSeconds }}' - --fail-on-failed-tests '${{ parameters.failWorkItemsWithFailedTests }}' - --allow-no-helix-jobs '${{ parameters.allowNoHelixJobs }}' - --use-fully-qualified-test-name '${{ parameters.useFullyQualifiedTestName }}' - --max-wait-minutes "$((${{ parameters.timeoutInMinutes }} - 5))" # Set the tool's timeout slightly lower than the Azure DevOps job timeout to allow it to exit gracefully. - --stage-name '$(System.StageName)' - --stage-attempt '$(System.StageAttempt)' - --job-attempt '$(System.JobAttempt)' - --test-result-upload-parallelism '${{ parameters.testResultUploadParallelism }}' - ) - - organization='${{ parameters.organization }}' - repository='${{ parameters.repository }}' - testResultAttachmentMode='${{ parameters.testResultAttachmentMode }}' - - # Fall back to Azure DevOps-provided environment variables when the caller did not - # supply organization / repository explicitly. BUILD_REPOSITORY_NAME is typically - # 'owner/repo' for GitHub-backed builds and 'owner-repo' for internal builds. - if [ -z "$organization" ] || [ -z "$repository" ]; then - buildRepoName="${BUILD_REPOSITORY_NAME:-}" - if [ -n "$buildRepoName" ] && [[ "$buildRepoName" == */* ]]; then - repoOwner="${buildRepoName%%/*}" - repoName="${buildRepoName#*/}" - elif [ -n "$buildRepoName" ] && [[ "$buildRepoName" == *-* ]]; then - repoOwner="${buildRepoName%%-*}" - repoName="${buildRepoName#*-}" - fi - - if [ -n "${repoOwner:-}" ] && [ -n "${repoName:-}" ]; then - if [ -z "$organization" ]; then organization="$repoOwner"; fi - if [ -z "$repository" ]; then repository="$repoName"; fi - fi - fi - - if [ -n "$organization" ]; then toolArgs+=( --organization "$organization" ); fi - if [ -n "$repository" ]; then toolArgs+=( --repository "$repository" ); fi - if [ -n "$testResultAttachmentMode" ]; then - toolArgs+=( --test-result-attachment-mode "$testResultAttachmentMode" ) - fi - - # Build.Reason and Build.SourceBranch are required to derive the Helix source filter - # the same way the Helix SDK submitter does (PR -> 'pr', internal -> 'official', - # otherwise -> 'ci'). Without these, manually-queued / scheduled / CI builds would - # be looked up under the wrong source prefix and find zero jobs. - toolArgs+=( --build-reason "$(Build.Reason)" ) - toolArgs+=( --source-branch "$(Build.SourceBranch)" ) - - if [ -n '${{ parameters.toolNupkgArtifactName }}' ]; then - # Tool was installed from a local nupkg; run the DLL via the repo-local dotnet. - export DOTNET_ROOT="$(Build.SourcesDirectory)/.dotnet" - ./eng/common/dotnet.sh exec "$(HelixJobMonitorDll)" "${toolArgs[@]}" - else - # Tool was restored from the local .config/dotnet-tools.json manifest; invoke it - # through the manifest from the repo root. - pushd "$BUILD_SOURCESDIRECTORY" > /dev/null - trap 'popd > /dev/null' EXIT - ./eng/common/dotnet.sh tool run '${{ parameters.toolCommand }}' -- "${toolArgs[@]}" - fi - displayName: Monitor Helix Jobs - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - ${{ if eq(parameters.useEntraAuthentication, false) }}: + - ${{ if eq(parameters.useEntraAuthentication, true) }}: + - task: AzureCLI@2 + displayName: Monitor Helix Jobs + inputs: + azureSubscription: ${{ parameters.azureSubscription }} + keepAzSessionActive: true + scriptType: bash + scriptLocation: inlineScript + inlineScript: | + set -euo pipefail + + # AzureCLI@2 replaces SYSTEM_ACCESSTOKEN with the service connection token. + export SYSTEM_ACCESSTOKEN="$HELIX_AZDO_ACCESSTOKEN" + bash "$BUILD_SOURCESDIRECTORY/eng/common/run-helix-job-monitor.sh" + env: + HELIX_AZDO_ACCESSTOKEN: $(System.AccessToken) + + - ${{ if eq(parameters.useEntraAuthentication, false) }}: + - bash: bash "$BUILD_SOURCESDIRECTORY/eng/common/run-helix-job-monitor.sh" + displayName: Monitor Helix Jobs + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) HELIX_ACCESSTOKEN: ${{ parameters.helixAccessToken }} - ${{ if eq(parameters.useEntraAuthentication, true) }}: - AZURESUBSCRIPTION_CLIENT_ID: $(HelixEntraClientId) - AZURESUBSCRIPTION_TENANT_ID: $(HelixEntraTenantId) - AZURESUBSCRIPTION_SERVICE_CONNECTION_ID: ${{ parameters.azureSubscription }} diff --git a/eng/common/core-templates/job/onelocbuild.yml b/eng/common/core-templates/job/onelocbuild.yml index d7a69f1c76e..adcdfa17536 100644 --- a/eng/common/core-templates/job/onelocbuild.yml +++ b/eng/common/core-templates/job/onelocbuild.yml @@ -10,9 +10,9 @@ parameters: # GitHub App authentication for the OneLoc check-in PR. GitHubAppServiceConnection: 'dnceng-oneloc-githubapp' - GitHubAppClientId: 'Iv23lijBU8x3gc9lDOc9' GitHubAppKeyVaultName: 'EngKeyVault' - GitHubAppKeyName: 'oneloc-localization-app-key' + GitHubAppIdSecretName: 'oneloc-localization-app-app-id' + GitHubAppPrivateKeySecretName: 'oneloc-localization-app-app-private-key' SourcesDirectory: $(System.DefaultWorkingDirectory) CreatePr: true @@ -101,8 +101,8 @@ jobs: ${{ else }}: azureSubscription: ${{ parameters.GitHubAppServiceConnection }} keyVaultName: ${{ parameters.GitHubAppKeyVaultName }} - keyName: ${{ parameters.GitHubAppKeyName }} - appClientId: ${{ parameters.GitHubAppClientId }} + appIdSecretName: ${{ parameters.GitHubAppIdSecretName }} + appPrivateKeySecretName: ${{ parameters.GitHubAppPrivateKeySecretName }} installationOwner: ${{ parameters.GitHubOrg }} outputVariableName: 'GitHubAppInstallationToken' condition: ${{ parameters.condition }} diff --git a/eng/common/core-templates/steps/get-github-app-token.yml b/eng/common/core-templates/steps/get-github-app-token.yml index 6d42a48d3c3..3eeb5a4c1bc 100644 --- a/eng/common/core-templates/steps/get-github-app-token.yml +++ b/eng/common/core-templates/steps/get-github-app-token.yml @@ -1,13 +1,11 @@ # Mints a short-lived GitHub App installation access token by signing a JWT -# with a private key stored in Azure Key Vault (RSA, RS256). The JWT is -# exchanged with the GitHub API for a token scoped to a single installation. +# with an RSA private key (RS256). The JWT is exchanged with the GitHub API +# for a token scoped to a single installation. # # Requirements (per GitHub App you want to authenticate as): -# - A GitHub App with its private key uploaded into Key Vault as an RSA key -# (PEM converted to a key, NOT stored as a secret). -# - The Azure service connection passed via `azureSubscription` must be -# granted the `Key Vault Crypto User` role (or at minimum `Sign` action) -# on that key. +# - A GitHub App ID and PEM private key stored as Azure Key Vault secrets. +# - The Azure service connection passed via `azureSubscription` must have +# `Get` access to those two secrets. # - The App must be installed on the target organization/account # (`installationOwner`) with the permissions/repositories you need. # @@ -17,23 +15,18 @@ # enterprise classic-PAT lifetime policy. parameters: -# Azure DevOps service connection (federated) that can call -# `az keyvault key sign` on the App's signing key. +# Azure DevOps service connection (federated) that can read the App credentials. - name: azureSubscription type: string -# Name of the Key Vault that holds the GitHub App's RSA signing key. +# Name of the Key Vault holding Secret Manager's github-app-secret projections. - name: keyVaultName type: string -# Name of the RSA key inside the Key Vault (the App's private key). -- name: keyName +- name: appIdSecretName type: string -# The GitHub App's Client ID (the value to put in the `iss` JWT claim). -# Prefer this over the numeric App ID; GitHub accepts either, but Client ID -# is the documented form going forward. -- name: appClientId +- name: appPrivateKeySecretName type: string # Login of the organization or user account whose installation we should @@ -73,7 +66,7 @@ steps: inlineScript: | & "$(System.DefaultWorkingDirectory)/eng/common/Get-GitHubAppToken.ps1" ` -KeyVaultName '${{ parameters.keyVaultName }}' ` - -KeyName '${{ parameters.keyName }}' ` - -AppClientId '${{ parameters.appClientId }}' ` + -AppIdSecretName '${{ parameters.appIdSecretName }}' ` + -AppPrivateKeySecretName '${{ parameters.appPrivateKeySecretName }}' ` -InstallationOwner '${{ parameters.installationOwner }}' ` -OutputVariableName '${{ parameters.outputVariableName }}' diff --git a/eng/common/core-templates/steps/send-to-helix.yml b/eng/common/core-templates/steps/send-to-helix.yml index 3951e47104a..41a94526ea0 100644 --- a/eng/common/core-templates/steps/send-to-helix.yml +++ b/eng/common/core-templates/steps/send-to-helix.yml @@ -7,7 +7,7 @@ parameters: HelixAccessToken: '' # optional -- legacy access token; not forwarded when HelixUseEntraAuthentication is true HelixUseEntraAuthentication: false # optional -- use refreshable Entra authentication instead of a PAT or anonymous access HelixEntraScope: '' # optional -- explicit Entra scope required for custom HelixBaseUri hosts - HelixAzureSubscription: '' # required when HelixUseEntraAuthentication is true -- Azure service connection ID authorized for Helix + HelixAzureSubscription: '' # required when HelixUseEntraAuthentication is true -- workload identity federation service connection name authorized for Helix HelixProjectPath: 'eng/common/helixpublish.proj' # optional -- path to the project file to build relative to BUILD_SOURCESDIRECTORY HelixProjectArguments: '' # optional -- arguments passed to the build command HelixConfiguration: '' # optional -- additional property attached to a job @@ -42,108 +42,178 @@ steps: - ${{ if eq(parameters.HelixUseEntraAuthentication, true) }}: - task: AzureCLI@2 - displayName: Initialize Helix Entra authentication + displayName: ${{ parameters.DisplayNamePrefix }} (Windows) inputs: azureSubscription: ${{ parameters.HelixAzureSubscription }} - addSpnToEnvironment: true + keepAzSessionActive: true scriptType: pscore scriptLocation: inlineScript inlineScript: | - if ([string]::IsNullOrWhiteSpace($env:servicePrincipalId) -or [string]::IsNullOrWhiteSpace($env:tenantId)) { - throw "The Helix Azure service connection did not provide a service principal or tenant ID." - } + # AzureCLI@2 replaces SYSTEM_ACCESSTOKEN with the service connection token. + $env:SYSTEM_ACCESSTOKEN = $env:HELIX_AZDO_ACCESSTOKEN - Write-Host "##vso[task.setvariable variable=HelixEntraClientId]$env:servicePrincipalId" - Write-Host "##vso[task.setvariable variable=HelixEntraTenantId]$env:tenantId" - condition: ${{ parameters.condition }} + $(Build.SourcesDirectory)\eng\common\msbuild.ps1 ` + $(Build.SourcesDirectory)/${{ parameters.HelixProjectPath }} ` + /restore ` + /p:TreatWarningsAsErrors=false ` + /p:EnableHelixJobMonitor=${{ parameters.UseHelixMonitor }} ` + /p:HelixUseEntraAuthentication=${{ parameters.HelixUseEntraAuthentication }} ` + ${{ parameters.HelixProjectArguments }} ` + /t:Test ` + /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog + env: + BuildConfig: $(_BuildConfig) + HelixSource: ${{ parameters.HelixSource }} + HelixType: ${{ parameters.HelixType }} + HelixBuild: ${{ parameters.HelixBuild }} + HelixConfiguration: ${{ parameters.HelixConfiguration }} + HelixTargetQueues: ${{ parameters.HelixTargetQueues }} + HelixEntraScope: ${{ parameters.HelixEntraScope }} + HelixPreCommands: ${{ parameters.HelixPreCommands }} + HelixPostCommands: ${{ parameters.HelixPostCommands }} + WorkItemDirectory: ${{ parameters.WorkItemDirectory }} + WorkItemCommand: ${{ parameters.WorkItemCommand }} + WorkItemTimeout: ${{ parameters.WorkItemTimeout }} + CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} + XUnitProjects: ${{ parameters.XUnitProjects }} + XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} + XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} + XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} + XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} + IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} + DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} + DotNetCliVersion: ${{ parameters.DotNetCliVersion }} + WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + HelixBaseUri: ${{ parameters.HelixBaseUri }} + Creator: ${{ parameters.Creator }} + HELIX_AZDO_ACCESSTOKEN: $(System.AccessToken) + condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} + - task: AzureCLI@2 + displayName: ${{ parameters.DisplayNamePrefix }} (Unix) + inputs: + azureSubscription: ${{ parameters.HelixAzureSubscription }} + keepAzSessionActive: true + scriptType: bash + scriptLocation: inlineScript + inlineScript: | + # AzureCLI@2 replaces SYSTEM_ACCESSTOKEN with the service connection token. + export SYSTEM_ACCESSTOKEN="$HELIX_AZDO_ACCESSTOKEN" + + $(Build.SourcesDirectory)/eng/common/msbuild.sh \ + $(Build.SourcesDirectory)/${{ parameters.HelixProjectPath }} \ + /restore \ + /p:TreatWarningsAsErrors=false \ + /p:EnableHelixJobMonitor=${{ parameters.UseHelixMonitor }} \ + /p:HelixUseEntraAuthentication=${{ parameters.HelixUseEntraAuthentication }} \ + ${{ parameters.HelixProjectArguments }} \ + /t:Test \ + /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog + env: + BuildConfig: $(_BuildConfig) + HelixSource: ${{ parameters.HelixSource }} + HelixType: ${{ parameters.HelixType }} + HelixBuild: ${{ parameters.HelixBuild }} + HelixConfiguration: ${{ parameters.HelixConfiguration }} + HelixTargetQueues: ${{ parameters.HelixTargetQueues }} + HelixEntraScope: ${{ parameters.HelixEntraScope }} + HelixPreCommands: ${{ parameters.HelixPreCommands }} + HelixPostCommands: ${{ parameters.HelixPostCommands }} + WorkItemDirectory: ${{ parameters.WorkItemDirectory }} + WorkItemCommand: ${{ parameters.WorkItemCommand }} + WorkItemTimeout: ${{ parameters.WorkItemTimeout }} + CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} + XUnitProjects: ${{ parameters.XUnitProjects }} + XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} + XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} + XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} + XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} + IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} + DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} + DotNetCliVersion: ${{ parameters.DotNetCliVersion }} + WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + HelixBaseUri: ${{ parameters.HelixBaseUri }} + Creator: ${{ parameters.Creator }} + HELIX_AZDO_ACCESSTOKEN: $(System.AccessToken) + condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} - - powershell: > - $(Build.SourcesDirectory)\eng\common\msbuild.ps1 - $(Build.SourcesDirectory)/${{ parameters.HelixProjectPath }} - /restore - /p:TreatWarningsAsErrors=false - /p:EnableHelixJobMonitor=${{ parameters.UseHelixMonitor }} - /p:HelixUseEntraAuthentication=${{ parameters.HelixUseEntraAuthentication }} - ${{ parameters.HelixProjectArguments }} - /t:Test - /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog - displayName: ${{ parameters.DisplayNamePrefix }} (Windows) - env: - BuildConfig: $(_BuildConfig) - HelixSource: ${{ parameters.HelixSource }} - HelixType: ${{ parameters.HelixType }} - HelixBuild: ${{ parameters.HelixBuild }} - HelixConfiguration: ${{ parameters.HelixConfiguration }} - HelixTargetQueues: ${{ parameters.HelixTargetQueues }} - ${{ if eq(parameters.HelixUseEntraAuthentication, false) }}: + - ${{ if eq(parameters.HelixUseEntraAuthentication, false) }}: + - powershell: > + $(Build.SourcesDirectory)\eng\common\msbuild.ps1 + $(Build.SourcesDirectory)/${{ parameters.HelixProjectPath }} + /restore + /p:TreatWarningsAsErrors=false + /p:EnableHelixJobMonitor=${{ parameters.UseHelixMonitor }} + /p:HelixUseEntraAuthentication=${{ parameters.HelixUseEntraAuthentication }} + ${{ parameters.HelixProjectArguments }} + /t:Test + /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog + displayName: ${{ parameters.DisplayNamePrefix }} (Windows) + env: + BuildConfig: $(_BuildConfig) + HelixSource: ${{ parameters.HelixSource }} + HelixType: ${{ parameters.HelixType }} + HelixBuild: ${{ parameters.HelixBuild }} + HelixConfiguration: ${{ parameters.HelixConfiguration }} + HelixTargetQueues: ${{ parameters.HelixTargetQueues }} HelixAccessToken: ${{ parameters.HelixAccessToken }} - ${{ if eq(parameters.HelixUseEntraAuthentication, true) }}: - AZURESUBSCRIPTION_CLIENT_ID: $(HelixEntraClientId) - AZURESUBSCRIPTION_TENANT_ID: $(HelixEntraTenantId) - AZURESUBSCRIPTION_SERVICE_CONNECTION_ID: ${{ parameters.HelixAzureSubscription }} - HelixEntraScope: ${{ parameters.HelixEntraScope }} - HelixPreCommands: ${{ parameters.HelixPreCommands }} - HelixPostCommands: ${{ parameters.HelixPostCommands }} - WorkItemDirectory: ${{ parameters.WorkItemDirectory }} - WorkItemCommand: ${{ parameters.WorkItemCommand }} - WorkItemTimeout: ${{ parameters.WorkItemTimeout }} - CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} - XUnitProjects: ${{ parameters.XUnitProjects }} - XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} - XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} - XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} - XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} - IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} - DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} - DotNetCliVersion: ${{ parameters.DotNetCliVersion }} - WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} - HelixBaseUri: ${{ parameters.HelixBaseUri }} - Creator: ${{ parameters.Creator }} - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT')) - continueOnError: ${{ parameters.continueOnError }} - - script: > - $(Build.SourcesDirectory)/eng/common/msbuild.sh - $(Build.SourcesDirectory)/${{ parameters.HelixProjectPath }} - /restore - /p:TreatWarningsAsErrors=false - /p:EnableHelixJobMonitor=${{ parameters.UseHelixMonitor }} - /p:HelixUseEntraAuthentication=${{ parameters.HelixUseEntraAuthentication }} - ${{ parameters.HelixProjectArguments }} - /t:Test - /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog - displayName: ${{ parameters.DisplayNamePrefix }} (Unix) - env: - BuildConfig: $(_BuildConfig) - HelixSource: ${{ parameters.HelixSource }} - HelixType: ${{ parameters.HelixType }} - HelixBuild: ${{ parameters.HelixBuild }} - HelixConfiguration: ${{ parameters.HelixConfiguration }} - HelixTargetQueues: ${{ parameters.HelixTargetQueues }} - ${{ if eq(parameters.HelixUseEntraAuthentication, false) }}: + HelixPreCommands: ${{ parameters.HelixPreCommands }} + HelixPostCommands: ${{ parameters.HelixPostCommands }} + WorkItemDirectory: ${{ parameters.WorkItemDirectory }} + WorkItemCommand: ${{ parameters.WorkItemCommand }} + WorkItemTimeout: ${{ parameters.WorkItemTimeout }} + CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} + XUnitProjects: ${{ parameters.XUnitProjects }} + XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} + XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} + XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} + XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} + IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} + DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} + DotNetCliVersion: ${{ parameters.DotNetCliVersion }} + WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + HelixBaseUri: ${{ parameters.HelixBaseUri }} + Creator: ${{ parameters.Creator }} + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} + - script: > + $(Build.SourcesDirectory)/eng/common/msbuild.sh + $(Build.SourcesDirectory)/${{ parameters.HelixProjectPath }} + /restore + /p:TreatWarningsAsErrors=false + /p:EnableHelixJobMonitor=${{ parameters.UseHelixMonitor }} + /p:HelixUseEntraAuthentication=${{ parameters.HelixUseEntraAuthentication }} + ${{ parameters.HelixProjectArguments }} + /t:Test + /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog + displayName: ${{ parameters.DisplayNamePrefix }} (Unix) + env: + BuildConfig: $(_BuildConfig) + HelixSource: ${{ parameters.HelixSource }} + HelixType: ${{ parameters.HelixType }} + HelixBuild: ${{ parameters.HelixBuild }} + HelixConfiguration: ${{ parameters.HelixConfiguration }} + HelixTargetQueues: ${{ parameters.HelixTargetQueues }} HelixAccessToken: ${{ parameters.HelixAccessToken }} - ${{ if eq(parameters.HelixUseEntraAuthentication, true) }}: - AZURESUBSCRIPTION_CLIENT_ID: $(HelixEntraClientId) - AZURESUBSCRIPTION_TENANT_ID: $(HelixEntraTenantId) - AZURESUBSCRIPTION_SERVICE_CONNECTION_ID: ${{ parameters.HelixAzureSubscription }} - HelixEntraScope: ${{ parameters.HelixEntraScope }} - HelixPreCommands: ${{ parameters.HelixPreCommands }} - HelixPostCommands: ${{ parameters.HelixPostCommands }} - WorkItemDirectory: ${{ parameters.WorkItemDirectory }} - WorkItemCommand: ${{ parameters.WorkItemCommand }} - WorkItemTimeout: ${{ parameters.WorkItemTimeout }} - CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} - XUnitProjects: ${{ parameters.XUnitProjects }} - XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} - XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} - XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} - XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} - IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} - DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} - DotNetCliVersion: ${{ parameters.DotNetCliVersion }} - WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} - HelixBaseUri: ${{ parameters.HelixBaseUri }} - Creator: ${{ parameters.Creator }} - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT')) - continueOnError: ${{ parameters.continueOnError }} + HelixPreCommands: ${{ parameters.HelixPreCommands }} + HelixPostCommands: ${{ parameters.HelixPostCommands }} + WorkItemDirectory: ${{ parameters.WorkItemDirectory }} + WorkItemCommand: ${{ parameters.WorkItemCommand }} + WorkItemTimeout: ${{ parameters.WorkItemTimeout }} + CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} + XUnitProjects: ${{ parameters.XUnitProjects }} + XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} + XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} + XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} + XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} + IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} + DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} + DotNetCliVersion: ${{ parameters.DotNetCliVersion }} + WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + HelixBaseUri: ${{ parameters.HelixBaseUri }} + Creator: ${{ parameters.Creator }} + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} diff --git a/eng/common/run-helix-job-monitor.sh b/eng/common/run-helix-job-monitor.sh new file mode 100644 index 00000000000..c79bd98e3f7 --- /dev/null +++ b/eng/common/run-helix-job-monitor.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +set -euo pipefail + +scriptroot="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +. "$scriptroot/pipeline-logging-functions.sh" + +toolArgs=( + --helix-base-uri "$HELIX_MONITOR_BASE_URI" + --use-entra-authentication "$HELIX_MONITOR_USE_ENTRA_AUTHENTICATION" + --helix-entra-scope "$HELIX_MONITOR_ENTRA_SCOPE" + --polling-interval-seconds "$HELIX_MONITOR_POLLING_INTERVAL_SECONDS" + --fail-on-failed-tests "$HELIX_MONITOR_FAIL_ON_FAILED_TESTS" + --allow-no-helix-jobs "$HELIX_MONITOR_ALLOW_NO_HELIX_JOBS" + --use-fully-qualified-test-name "$HELIX_MONITOR_USE_FULLY_QUALIFIED_TEST_NAME" + --max-wait-minutes "$((HELIX_MONITOR_TIMEOUT_IN_MINUTES - 5))" + --stage-name "$SYSTEM_STAGENAME" + --stage-attempt "$SYSTEM_STAGEATTEMPT" + --job-attempt "$SYSTEM_JOBATTEMPT" + --test-result-upload-parallelism "$HELIX_MONITOR_TEST_RESULT_UPLOAD_PARALLELISM" +) + +organization="${HELIX_MONITOR_ORGANIZATION:-}" +repository="${HELIX_MONITOR_REPOSITORY:-}" + +# Fall back to Azure DevOps-provided environment variables when the caller did not +# supply organization or repository explicitly. +if [ -z "$organization" ] || [ -z "$repository" ]; then + buildRepoName="${BUILD_REPOSITORY_NAME:-}" + if [ -n "$buildRepoName" ] && [[ "$buildRepoName" == */* ]]; then + repoOwner="${buildRepoName%%/*}" + repoName="${buildRepoName#*/}" + elif [ -n "$buildRepoName" ] && [[ "$buildRepoName" == *-* ]]; then + repoOwner="${buildRepoName%%-*}" + repoName="${buildRepoName#*-}" + fi + + if [ -n "${repoOwner:-}" ] && [ -n "${repoName:-}" ]; then + if [ -z "$organization" ]; then organization="$repoOwner"; fi + if [ -z "$repository" ]; then repository="$repoName"; fi + fi +fi + +if [ -n "$organization" ]; then toolArgs+=( --organization "$organization" ); fi +if [ -n "$repository" ]; then toolArgs+=( --repository "$repository" ); fi +if [ -n "${HELIX_MONITOR_TEST_RESULT_ATTACHMENT_MODE:-}" ]; then + toolArgs+=( --test-result-attachment-mode "$HELIX_MONITOR_TEST_RESULT_ATTACHMENT_MODE" ) +fi + +# These values let the monitor derive the same Helix source filter as the SDK submitter. +toolArgs+=( --build-reason "$BUILD_REASON" ) +toolArgs+=( --source-branch "$BUILD_SOURCEBRANCH" ) + +cd "$BUILD_SOURCESDIRECTORY" +exitCode=0 +if [ -n "${HELIX_MONITOR_TOOL_NUPKG_ARTIFACT_NAME:-}" ]; then + export DOTNET_ROOT="$BUILD_SOURCESDIRECTORY/.dotnet" + ./eng/common/dotnet.sh exec "$HELIXJOBMONITORDLL" "${toolArgs[@]}" || exitCode=$? +else + ./eng/common/dotnet.sh tool run "$HELIX_MONITOR_TOOL_COMMAND" -- "${toolArgs[@]}" || exitCode=$? +fi + +if [ "$exitCode" -ne 0 ]; then + Write-PipelineTelemetryError -force -category 'Helix' "Helix job monitor failed (exit code '$exitCode')." + exit "$exitCode" +fi diff --git a/global.json b/global.json index fb1725a1820..f56babbff7e 100644 --- a/global.json +++ b/global.json @@ -18,7 +18,7 @@ } }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26505.113", - "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.26505.113" + "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26508.104", + "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.26508.104" } } From 318bf8b87a4906d20df149161d26f90932f4abdf Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Oct 2026 12:19:49 -0700 Subject: [PATCH 08/11] [release/10.0] Report CommandSource.ExecuteDelete for ExecuteDelete commands (#39197) Fixes #39186 Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- ...alShapedQueryCompilingExpressionVisitor.cs | 2 +- .../CommandInterceptionTestBase.cs | 46 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs index df54cef0bb0..34e84bb2504 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs @@ -89,7 +89,7 @@ protected override Expression VisitExtension(Expression extensionExpression) return extensionExpression switch { UpdateExpression updateExpression => GenerateNonQueryShaper(updateExpression.ApplyTags(_tags), CommandSource.ExecuteUpdate), - DeleteExpression deleteExpression => GenerateNonQueryShaper(deleteExpression.ApplyTags(_tags), CommandSource.ExecuteUpdate), + DeleteExpression deleteExpression => GenerateNonQueryShaper(deleteExpression.ApplyTags(_tags), CommandSource.ExecuteDelete), _ => base.VisitExtension(extensionExpression) }; diff --git a/test/EFCore.Relational.Specification.Tests/CommandInterceptionTestBase.cs b/test/EFCore.Relational.Specification.Tests/CommandInterceptionTestBase.cs index aa1081e8ac2..f01b98609f5 100644 --- a/test/EFCore.Relational.Specification.Tests/CommandInterceptionTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/CommandInterceptionTestBase.cs @@ -101,6 +101,52 @@ public virtual async Task Intercept_non_query_passively(bool async, bool inject) protected class PassiveNonQueryCommandInterceptor() : CommandInterceptorBase(DbCommandMethod.ExecuteNonQuery); + [ConditionalTheory, InlineData(false, false), InlineData(true, false), InlineData(false, true), InlineData(true, true)] + public virtual async Task Intercept_ExecuteDelete_passively(bool async, bool inject) + { + var (context, interceptor) = await CreateContextAsync(inject); + using (context) + { + using (context.Database.BeginTransaction()) + { + using var listener = Fixture.SubscribeToDiagnosticListener(context.ContextId); + var query = context.Set().Where(e => e.Id == 77); + var result = async + ? await query.ExecuteDeleteAsync() + : query.ExecuteDelete(); + + Assert.Equal(1, result); + + AssertNormalOutcome(context, interceptor, async, CommandSource.ExecuteDelete); + + AssertExecutedEvents(listener); + } + } + } + + [ConditionalTheory, InlineData(false, false), InlineData(true, false), InlineData(false, true), InlineData(true, true)] + public virtual async Task Intercept_ExecuteUpdate_passively(bool async, bool inject) + { + var (context, interceptor) = await CreateContextAsync(inject); + using (context) + { + using (context.Database.BeginTransaction()) + { + using var listener = Fixture.SubscribeToDiagnosticListener(context.ContextId); + var query = context.Set().Where(e => e.Id == 77); + var result = async + ? await query.ExecuteUpdateAsync(s => s.SetProperty(e => e.Type, "White Hole")) + : query.ExecuteUpdate(s => s.SetProperty(e => e.Type, "White Hole")); + + Assert.Equal(1, result); + + AssertNormalOutcome(context, interceptor, async, CommandSource.ExecuteUpdate); + + AssertExecutedEvents(listener); + } + } + } + [ConditionalTheory, InlineData(false, false), InlineData(true, false), InlineData(false, true), InlineData(true, true)] public virtual async Task Intercept_query_to_suppress_execution(bool async, bool inject) { From 6133917412ecea3e1e6b2a50148fc394dd58ae48 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 16:42:28 -0700 Subject: [PATCH 09/11] [release/10.0] Source code updates from dotnet/dotnet (#39207) Updated Dependencies: Microsoft.DotNet.Arcade.Sdk, Microsoft.DotNet.Build.Tasks.Templating, Microsoft.DotNet.Helix.Sdk (Version 10.0.0-beta.26508.104 -> 10.0.0-beta.26508.128) Microsoft.Extensions.Caching.Memory, Microsoft.Extensions.Configuration, Microsoft.Extensions.Configuration.Abstractions, Microsoft.Extensions.Configuration.EnvironmentVariables, Microsoft.Extensions.Configuration.Json, Microsoft.Extensions.DependencyInjection, Microsoft.Extensions.DependencyModel, Microsoft.Extensions.Logging, Microsoft.NETCore.App.Ref, System.Formats.Asn1, System.Runtime.Caching, System.Text.Encodings.Web, System.Text.Json (Version 10.0.14 -> 10.0.14) Microsoft.Extensions.HostFactoryResolver.Sources, Microsoft.NETCore.Platforms (Version 10.0.14-servicing.26508.104 -> 10.0.14-servicing.26508.128) [[ commit created by automation ]] --- NuGet.config | 2 +- eng/Version.Details.props | 10 ++++---- eng/Version.Details.xml | 48 +++++++++++++++++++-------------------- global.json | 4 ++-- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/NuGet.config b/NuGet.config index db0ad66ff68..41eb2056e76 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,7 +4,7 @@ - + diff --git a/eng/Version.Details.props b/eng/Version.Details.props index a1a16f27d70..87558d746db 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -6,9 +6,9 @@ This file should be imported by eng/Versions.props - 10.0.0-beta.26508.104 - 10.0.0-beta.26508.104 - 10.0.0-beta.26508.104 + 10.0.0-beta.26508.128 + 10.0.0-beta.26508.128 + 10.0.0-beta.26508.128 10.0.14 10.0.14 10.0.14 @@ -16,10 +16,10 @@ This file should be imported by eng/Versions.props 10.0.14 10.0.14 10.0.14 - 10.0.14-servicing.26508.104 + 10.0.14-servicing.26508.128 10.0.14 10.0.14 - 10.0.14-servicing.26508.104 + 10.0.14-servicing.26508.128 10.0.14 10.0.14 10.0.14 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 7f297dcb82b..b96079265aa 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,80 +1,80 @@ - + https://github.com/dotnet/dotnet - 8d12953c7e56fbc03f5b22372c0013d69c908517 + 476261becdd44d137ca9c688d059788d24330975 https://github.com/dotnet/dotnet - 8d12953c7e56fbc03f5b22372c0013d69c908517 + 476261becdd44d137ca9c688d059788d24330975 https://github.com/dotnet/dotnet - 8d12953c7e56fbc03f5b22372c0013d69c908517 + 476261becdd44d137ca9c688d059788d24330975 https://github.com/dotnet/dotnet - 8d12953c7e56fbc03f5b22372c0013d69c908517 + 476261becdd44d137ca9c688d059788d24330975 https://github.com/dotnet/dotnet - 8d12953c7e56fbc03f5b22372c0013d69c908517 + 476261becdd44d137ca9c688d059788d24330975 https://github.com/dotnet/dotnet - 8d12953c7e56fbc03f5b22372c0013d69c908517 + 476261becdd44d137ca9c688d059788d24330975 https://github.com/dotnet/dotnet - 8d12953c7e56fbc03f5b22372c0013d69c908517 + 476261becdd44d137ca9c688d059788d24330975 - + https://github.com/dotnet/dotnet - 8d12953c7e56fbc03f5b22372c0013d69c908517 + 476261becdd44d137ca9c688d059788d24330975 https://github.com/dotnet/dotnet - 8d12953c7e56fbc03f5b22372c0013d69c908517 + 476261becdd44d137ca9c688d059788d24330975 https://github.com/dotnet/dotnet - 8d12953c7e56fbc03f5b22372c0013d69c908517 + 476261becdd44d137ca9c688d059788d24330975 - + https://github.com/dotnet/dotnet - 8d12953c7e56fbc03f5b22372c0013d69c908517 + 476261becdd44d137ca9c688d059788d24330975 https://github.com/dotnet/dotnet - 8d12953c7e56fbc03f5b22372c0013d69c908517 + 476261becdd44d137ca9c688d059788d24330975 https://github.com/dotnet/dotnet - 8d12953c7e56fbc03f5b22372c0013d69c908517 + 476261becdd44d137ca9c688d059788d24330975 https://github.com/dotnet/dotnet - 8d12953c7e56fbc03f5b22372c0013d69c908517 + 476261becdd44d137ca9c688d059788d24330975 https://github.com/dotnet/dotnet - 8d12953c7e56fbc03f5b22372c0013d69c908517 + 476261becdd44d137ca9c688d059788d24330975 - + https://github.com/dotnet/dotnet - 8d12953c7e56fbc03f5b22372c0013d69c908517 + 476261becdd44d137ca9c688d059788d24330975 - + https://github.com/dotnet/dotnet - 8d12953c7e56fbc03f5b22372c0013d69c908517 + 476261becdd44d137ca9c688d059788d24330975 - + https://github.com/dotnet/dotnet - 8d12953c7e56fbc03f5b22372c0013d69c908517 + 476261becdd44d137ca9c688d059788d24330975 diff --git a/global.json b/global.json index f56babbff7e..67ced961707 100644 --- a/global.json +++ b/global.json @@ -18,7 +18,7 @@ } }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26508.104", - "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.26508.104" + "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26508.128", + "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.26508.128" } } From d8388e5e5008747ab8df475131981086fa2b1758 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Oct 2026 18:49:52 +0000 Subject: [PATCH 10/11] Revert eng/common merge changes Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- eng/common/Get-GitHubAppToken.ps1 | 123 ++++++---------- .../core-templates/job/helix-job-monitor.yml | 35 +++-- eng/common/core-templates/job/onelocbuild.yml | 8 +- .../steps/get-github-app-token.yml | 29 ++-- .../core-templates/steps/send-to-helix.yml | 135 ++++-------------- eng/common/run-helix-job-monitor.sh | 65 --------- 6 files changed, 109 insertions(+), 286 deletions(-) delete mode 100644 eng/common/run-helix-job-monitor.sh diff --git a/eng/common/Get-GitHubAppToken.ps1 b/eng/common/Get-GitHubAppToken.ps1 index ec005e487c3..ea776bd6bc2 100644 --- a/eng/common/Get-GitHubAppToken.ps1 +++ b/eng/common/Get-GitHubAppToken.ps1 @@ -1,11 +1,13 @@ # Mints a short-lived GitHub App installation access token by signing a JWT -# with an RSA private key (RS256). The signed JWT is exchanged with the GitHub -# API for a token scoped to a single installation. +# with a private key stored in Azure Key Vault (RSA, RS256). The signed JWT is +# exchanged with the GitHub API for a token scoped to a single installation. # # Requirements: -# - A GitHub App ID and PEM private key stored as Azure Key Vault secrets. -# - The federated Azure service connection running this script must have -# `Get` access to those two secrets. +# - A GitHub App whose private key has been uploaded into Key Vault as an RSA +# key (the PEM converted to a Key Vault *key*, NOT stored as a secret). +# - The caller (the federated Azure service connection used to run this script) +# must have the `Key Vault Crypto User` role (or at minimum the `Sign` +# action) on that key. # - The App must be installed on the target organization/account # (`InstallationOwner`) with the permissions/repositories it needs. # @@ -14,17 +16,17 @@ [CmdletBinding()] param( - # Name of the Key Vault holding the GitHub App credentials. + # Name of the Key Vault that holds the GitHub App's RSA signing key. [Parameter(Mandatory = $true)] [string] $KeyVaultName, - # Secret Manager projection containing the GitHub App ID. + # Name of the RSA key inside the Key Vault (the App's private key). [Parameter(Mandatory = $true)] - [string] $AppIdSecretName, + [string] $KeyName, - # Secret Manager projection containing the PEM private key. + # The GitHub App's Client ID (the value to put in the `iss` JWT claim). [Parameter(Mandatory = $true)] - [string] $AppPrivateKeySecretName, + [string] $AppClientId, # Login of the organization or user account whose installation we should # mint the token for (e.g. `dotnet`, `microsoft`). @@ -37,69 +39,16 @@ param( [Parameter(Mandatory = $false)] [string] $OutputVariableName ) + $ErrorActionPreference = 'Stop' $PSNativeCommandUseErrorActionPreference = $true . $PSScriptRoot\pipeline-logging-functions.ps1 -if ($KeyVaultName -notmatch '^[A-Za-z][A-Za-z0-9-]{1,22}[A-Za-z0-9]$' -or $KeyVaultName.Contains('--')) { - Write-PipelineTelemetryError -Category 'Build' -Message "KeyVaultName '$KeyVaultName' is not a valid Azure Key Vault name." - exit 1 -} - function ConvertTo-Base64Url([byte[]] $bytes) { return [Convert]::ToBase64String($bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_') } -$previousNativeCommandErrorPreference = $PSNativeCommandUseErrorActionPreference -try { - # Azure CLI can emit non-fatal Python warnings to stderr. - $PSNativeCommandUseErrorActionPreference = $false - $keyVaultAccessToken = az account get-access-token ` - --resource https://vault.azure.net ` - --query accessToken ` - --output tsv ` - --only-show-errors - $tokenExitCode = $LASTEXITCODE -} -catch { - Write-PipelineTelemetryError -Category 'Build' -Message "Failed to acquire an Azure Key Vault access token: $_" - exit 1 -} -finally { - $PSNativeCommandUseErrorActionPreference = $previousNativeCommandErrorPreference -} -if ($tokenExitCode -ne 0 -or [string]::IsNullOrWhiteSpace($keyVaultAccessToken)) { - Write-PipelineTelemetryError -Category 'Build' -Message "'az account get-access-token' exited with code $tokenExitCode while acquiring an Azure Key Vault access token." - exit 1 -} - -function Get-KeyVaultSecret([string] $SecretName) { - # Use the data-plane REST API because `az keyvault secret show` can fail - # with Errno 22 on hosted Windows agents when reading these projections. - $escapedSecretName = [Uri]::EscapeDataString($SecretName) - $secretUri = "https://$KeyVaultName.vault.azure.net/secrets/$escapedSecretName`?api-version=7.4" - try { - $response = Invoke-RestMethod ` - -Uri $secretUri ` - -Headers @{ Authorization = "Bearer $keyVaultAccessToken" } ` - -Method Get - } - catch { - Write-PipelineTelemetryError -Category 'Build' -Message "Failed to read secret '$SecretName' from vault '$KeyVaultName': $_. Verify the secret exists and the service connection has 'Key Vault Secrets User' access to it." - exit 1 - } - if ([string]::IsNullOrWhiteSpace($response.value)) { - Write-PipelineTelemetryError -Category 'Build' -Message "Secret '$SecretName' in vault '$KeyVaultName' is empty." - exit 1 - } - return [string] $response.value -} - -Write-Host "Reading GitHub App credentials from vault '$KeyVaultName'..." -$appId = Get-KeyVaultSecret $AppIdSecretName -$privateKey = Get-KeyVaultSecret $AppPrivateKeySecretName - # Build JWT header and payload. Use [ordered] hashtables so JSON # serialization is deterministic. $jwtHeader = [ordered]@{ @@ -110,38 +59,46 @@ $now = [System.DateTimeOffset]::UtcNow $jwtPayload = [ordered]@{ iat = $now.AddMinutes(-1).ToUnixTimeSeconds() exp = $now.AddMinutes(5).ToUnixTimeSeconds() - iss = $appId + iss = $AppClientId } $headerEncoded = ConvertTo-Base64Url ([System.Text.Encoding]::UTF8.GetBytes(($jwtHeader | ConvertTo-Json -Compress))) $payloadEncoded = ConvertTo-Base64Url ([System.Text.Encoding]::UTF8.GetBytes(($jwtPayload | ConvertTo-Json -Compress))) $signingInput = "$headerEncoded.$payloadEncoded" -$sha256 = [System.Security.Cryptography.SHA256]::Create() -try { - $digestBytes = $sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($signingInput)) -} -finally { - $sha256.Dispose() -} +# Key Vault `sign` expects the *digest* (base64), not the raw bytes. +$sha256 = [System.Security.Cryptography.SHA256]::Create() +$digestBytes = $sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($signingInput)) +$digestBase64 = [Convert]::ToBase64String($digestBytes) -Write-Host 'Signing JWT with the GitHub App private key...' -$rsa = [System.Security.Cryptography.RSA]::Create() +Write-Host "Signing JWT with key '$KeyName' in vault '$KeyVaultName'..." +$previousNativeCommandErrorPreference = $PSNativeCommandUseErrorActionPreference try { - $rsa.ImportFromPem($privateKey) - $signatureBytes = $rsa.SignHash( - $digestBytes, - [System.Security.Cryptography.HashAlgorithmName]::SHA256, - [System.Security.Cryptography.RSASignaturePadding]::Pkcs1) - $signatureUrl = ConvertTo-Base64Url $signatureBytes + # Azure CLI can emit non-fatal Python warnings to stderr even when signing succeeds. + # Use the exit code to determine success for this invocation. + $PSNativeCommandUseErrorActionPreference = $false + $signatureBase64 = az keyvault key sign ` + --vault-name $KeyVaultName ` + --name $KeyName ` + --algorithm RS256 ` + --digest $digestBase64 ` + --query signature ` + --output tsv ` + --only-show-errors + $signExitCode = $LASTEXITCODE } catch { - Write-PipelineTelemetryError -Category 'Build' -Message "Failed to sign the GitHub App JWT with the supplied private key: $_" + Write-PipelineTelemetryError -Category 'Build' -Message "Failed to sign the JWT via Key Vault (key '$KeyName', vault '$KeyVaultName'): $_. Verify the service connection identity has the 'Key Vault Crypto User' role (Sign action) on the key." exit 1 } finally { - $rsa.Dispose() + $PSNativeCommandUseErrorActionPreference = $previousNativeCommandErrorPreference +} +if ($signExitCode -ne 0 -or [string]::IsNullOrWhiteSpace($signatureBase64)) { + Write-PipelineTelemetryError -Category 'Build' -Message "'az keyvault key sign' exited with code $signExitCode for key '$KeyName' in vault '$KeyVaultName'. Verify the service connection identity has the 'Key Vault Crypto User' role (Sign action) on the key." + exit 1 } +$signatureUrl = $signatureBase64.Trim().TrimEnd('=').Replace('+', '-').Replace('/', '_') $jwt = "$signingInput.$signatureUrl" $headers = @{ @@ -169,7 +126,7 @@ try { } while ($pageInstallationCount -eq 100) } catch { - Write-PipelineTelemetryError -Category 'Build' -Message "Failed to list GitHub App installations: $_. The signed JWT may be invalid or the App ID may be incorrect." + Write-PipelineTelemetryError -Category 'Build' -Message "Failed to list GitHub App installations: $_. The signed JWT may be invalid or the App's Client ID ('$AppClientId') may be incorrect." exit 1 } $matchingInstallations = @($installations | Where-Object { $_.account.login -ieq $InstallationOwner }) diff --git a/eng/common/core-templates/job/helix-job-monitor.yml b/eng/common/core-templates/job/helix-job-monitor.yml index 9182f076486..b1ad5d68a46 100644 --- a/eng/common/core-templates/job/helix-job-monitor.yml +++ b/eng/common/core-templates/job/helix-job-monitor.yml @@ -135,21 +135,6 @@ jobs: displayName: Monitor Helix Jobs timeoutInMinutes: ${{ parameters.timeoutInMinutes }} continueOnError: ${{ parameters.continueOnError }} - variables: - HELIX_MONITOR_BASE_URI: ${{ parameters.helixBaseUri }} - HELIX_MONITOR_USE_ENTRA_AUTHENTICATION: ${{ parameters.useEntraAuthentication }} - HELIX_MONITOR_ENTRA_SCOPE: ${{ parameters.helixEntraScope }} - HELIX_MONITOR_POLLING_INTERVAL_SECONDS: ${{ parameters.pollingIntervalSeconds }} - HELIX_MONITOR_FAIL_ON_FAILED_TESTS: ${{ parameters.failWorkItemsWithFailedTests }} - HELIX_MONITOR_ALLOW_NO_HELIX_JOBS: ${{ parameters.allowNoHelixJobs }} - HELIX_MONITOR_USE_FULLY_QUALIFIED_TEST_NAME: ${{ parameters.useFullyQualifiedTestName }} - HELIX_MONITOR_TIMEOUT_IN_MINUTES: ${{ parameters.timeoutInMinutes }} - HELIX_MONITOR_TEST_RESULT_UPLOAD_PARALLELISM: ${{ parameters.testResultUploadParallelism }} - HELIX_MONITOR_ORGANIZATION: ${{ parameters.organization }} - HELIX_MONITOR_REPOSITORY: ${{ parameters.repository }} - HELIX_MONITOR_TEST_RESULT_ATTACHMENT_MODE: ${{ parameters.testResultAttachmentMode }} - HELIX_MONITOR_TOOL_NUPKG_ARTIFACT_NAME: ${{ parameters.toolNupkgArtifactName }} - HELIX_MONITOR_TOOL_COMMAND: ${{ parameters.toolCommand }} ${{ if ne(length(parameters.dependsOn), 0) }}: dependsOn: ${{ parameters.dependsOn }} ${{ if ne(parameters.condition, '') }}: @@ -171,6 +156,22 @@ jobs: - pwsh: throw "azureSubscription must be set when useEntraAuthentication is true." displayName: Validate Helix Entra authentication + - ${{ if eq(parameters.useEntraAuthentication, true) }}: + - task: AzureCLI@2 + displayName: Initialize Helix Entra authentication + inputs: + azureSubscription: ${{ parameters.azureSubscription }} + addSpnToEnvironment: true + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + if ([string]::IsNullOrWhiteSpace($env:servicePrincipalId) -or [string]::IsNullOrWhiteSpace($env:tenantId)) { + throw "The Helix Azure service connection did not provide a service principal or tenant ID." + } + + Write-Host "##vso[task.setvariable variable=HelixEntraClientId]$env:servicePrincipalId" + Write-Host "##vso[task.setvariable variable=HelixEntraTenantId]$env:tenantId" + - ${{ if ne(parameters.toolNupkgArtifactName, '') }}: - task: DownloadPipelineArtifact@2 displayName: Download Helix Job Monitor artifact @@ -308,3 +309,7 @@ jobs: SYSTEM_ACCESSTOKEN: $(System.AccessToken) ${{ if eq(parameters.useEntraAuthentication, false) }}: HELIX_ACCESSTOKEN: ${{ parameters.helixAccessToken }} + ${{ if eq(parameters.useEntraAuthentication, true) }}: + AZURESUBSCRIPTION_CLIENT_ID: $(HelixEntraClientId) + AZURESUBSCRIPTION_TENANT_ID: $(HelixEntraTenantId) + AZURESUBSCRIPTION_SERVICE_CONNECTION_ID: ${{ parameters.azureSubscription }} diff --git a/eng/common/core-templates/job/onelocbuild.yml b/eng/common/core-templates/job/onelocbuild.yml index c859dc2b071..25af2787900 100644 --- a/eng/common/core-templates/job/onelocbuild.yml +++ b/eng/common/core-templates/job/onelocbuild.yml @@ -10,9 +10,9 @@ parameters: # GitHub App authentication for the OneLoc check-in PR. GitHubAppServiceConnection: 'dnceng-oneloc-githubapp' + GitHubAppClientId: 'Iv23lijBU8x3gc9lDOc9' GitHubAppKeyVaultName: 'EngKeyVault' - GitHubAppIdSecretName: 'oneloc-localization-app-app-id' - GitHubAppPrivateKeySecretName: 'oneloc-localization-app-app-private-key' + GitHubAppKeyName: 'oneloc-localization-app-key' SourcesDirectory: $(System.DefaultWorkingDirectory) CreatePr: true @@ -102,8 +102,8 @@ jobs: ${{ else }}: azureSubscription: ${{ parameters.GitHubAppServiceConnection }} keyVaultName: ${{ parameters.GitHubAppKeyVaultName }} - appIdSecretName: ${{ parameters.GitHubAppIdSecretName }} - appPrivateKeySecretName: ${{ parameters.GitHubAppPrivateKeySecretName }} + keyName: ${{ parameters.GitHubAppKeyName }} + appClientId: ${{ parameters.GitHubAppClientId }} installationOwner: ${{ parameters.GitHubOrg }} outputVariableName: 'GitHubAppInstallationToken' condition: ${{ parameters.condition }} diff --git a/eng/common/core-templates/steps/get-github-app-token.yml b/eng/common/core-templates/steps/get-github-app-token.yml index 3eeb5a4c1bc..6d42a48d3c3 100644 --- a/eng/common/core-templates/steps/get-github-app-token.yml +++ b/eng/common/core-templates/steps/get-github-app-token.yml @@ -1,11 +1,13 @@ # Mints a short-lived GitHub App installation access token by signing a JWT -# with an RSA private key (RS256). The JWT is exchanged with the GitHub API -# for a token scoped to a single installation. +# with a private key stored in Azure Key Vault (RSA, RS256). The JWT is +# exchanged with the GitHub API for a token scoped to a single installation. # # Requirements (per GitHub App you want to authenticate as): -# - A GitHub App ID and PEM private key stored as Azure Key Vault secrets. -# - The Azure service connection passed via `azureSubscription` must have -# `Get` access to those two secrets. +# - A GitHub App with its private key uploaded into Key Vault as an RSA key +# (PEM converted to a key, NOT stored as a secret). +# - The Azure service connection passed via `azureSubscription` must be +# granted the `Key Vault Crypto User` role (or at minimum `Sign` action) +# on that key. # - The App must be installed on the target organization/account # (`installationOwner`) with the permissions/repositories you need. # @@ -15,18 +17,23 @@ # enterprise classic-PAT lifetime policy. parameters: -# Azure DevOps service connection (federated) that can read the App credentials. +# Azure DevOps service connection (federated) that can call +# `az keyvault key sign` on the App's signing key. - name: azureSubscription type: string -# Name of the Key Vault holding Secret Manager's github-app-secret projections. +# Name of the Key Vault that holds the GitHub App's RSA signing key. - name: keyVaultName type: string -- name: appIdSecretName +# Name of the RSA key inside the Key Vault (the App's private key). +- name: keyName type: string -- name: appPrivateKeySecretName +# The GitHub App's Client ID (the value to put in the `iss` JWT claim). +# Prefer this over the numeric App ID; GitHub accepts either, but Client ID +# is the documented form going forward. +- name: appClientId type: string # Login of the organization or user account whose installation we should @@ -66,7 +73,7 @@ steps: inlineScript: | & "$(System.DefaultWorkingDirectory)/eng/common/Get-GitHubAppToken.ps1" ` -KeyVaultName '${{ parameters.keyVaultName }}' ` - -AppIdSecretName '${{ parameters.appIdSecretName }}' ` - -AppPrivateKeySecretName '${{ parameters.appPrivateKeySecretName }}' ` + -KeyName '${{ parameters.keyName }}' ` + -AppClientId '${{ parameters.appClientId }}' ` -InstallationOwner '${{ parameters.installationOwner }}' ` -OutputVariableName '${{ parameters.outputVariableName }}' diff --git a/eng/common/core-templates/steps/send-to-helix.yml b/eng/common/core-templates/steps/send-to-helix.yml index 8c76d8c2abe..62cce4d4ea2 100644 --- a/eng/common/core-templates/steps/send-to-helix.yml +++ b/eng/common/core-templates/steps/send-to-helix.yml @@ -41,121 +41,40 @@ steps: - ${{ if eq(parameters.HelixUseEntraAuthentication, true) }}: - task: AzureCLI@2 - displayName: ${{ parameters.DisplayNamePrefix }} (Windows) + displayName: Initialize Helix Entra authentication inputs: azureSubscription: ${{ parameters.HelixAzureSubscription }} - keepAzSessionActive: true + addSpnToEnvironment: true scriptType: pscore scriptLocation: inlineScript inlineScript: | - # AzureCLI@2 replaces SYSTEM_ACCESSTOKEN with the service connection token. - $env:SYSTEM_ACCESSTOKEN = $env:HELIX_AZDO_ACCESSTOKEN + if ([string]::IsNullOrWhiteSpace($env:servicePrincipalId) -or [string]::IsNullOrWhiteSpace($env:tenantId)) { + throw "The Helix Azure service connection did not provide a service principal or tenant ID." + } - $(Build.SourcesDirectory)\eng\common\msbuild.ps1 ` - $(Build.SourcesDirectory)/${{ parameters.HelixProjectPath }} ` - /restore ` - /p:TreatWarningsAsErrors=false ` - /p:EnableHelixJobMonitor=${{ parameters.UseHelixMonitor }} ` - /p:HelixUseEntraAuthentication=${{ parameters.HelixUseEntraAuthentication }} ` - ${{ parameters.HelixProjectArguments }} ` - /t:Test ` - /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog - env: - BuildConfig: $(_BuildConfig) - HelixSource: ${{ parameters.HelixSource }} - HelixType: ${{ parameters.HelixType }} - HelixBuild: ${{ parameters.HelixBuild }} - HelixConfiguration: ${{ parameters.HelixConfiguration }} - HelixTargetQueues: ${{ parameters.HelixTargetQueues }} - HelixEntraScope: ${{ parameters.HelixEntraScope }} - HelixPreCommands: ${{ parameters.HelixPreCommands }} - HelixPostCommands: ${{ parameters.HelixPostCommands }} - WorkItemDirectory: ${{ parameters.WorkItemDirectory }} - WorkItemCommand: ${{ parameters.WorkItemCommand }} - WorkItemTimeout: ${{ parameters.WorkItemTimeout }} - CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} - XUnitProjects: ${{ parameters.XUnitProjects }} - XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} - XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} - XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} - XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} - IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} - DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} - DotNetCliVersion: ${{ parameters.DotNetCliVersion }} - WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} - HelixBaseUri: ${{ parameters.HelixBaseUri }} - Creator: ${{ parameters.Creator }} - HELIX_AZDO_ACCESSTOKEN: $(System.AccessToken) - condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT')) - continueOnError: ${{ parameters.continueOnError }} - - task: AzureCLI@2 - displayName: ${{ parameters.DisplayNamePrefix }} (Unix) - inputs: - azureSubscription: ${{ parameters.HelixAzureSubscription }} - keepAzSessionActive: true - scriptType: bash - scriptLocation: inlineScript - inlineScript: | - # AzureCLI@2 replaces SYSTEM_ACCESSTOKEN with the service connection token. - export SYSTEM_ACCESSTOKEN="$HELIX_AZDO_ACCESSTOKEN" - - $(Build.SourcesDirectory)/eng/common/msbuild.sh \ - $(Build.SourcesDirectory)/${{ parameters.HelixProjectPath }} \ - /restore \ - /p:TreatWarningsAsErrors=false \ - /p:EnableHelixJobMonitor=${{ parameters.UseHelixMonitor }} \ - /p:HelixUseEntraAuthentication=${{ parameters.HelixUseEntraAuthentication }} \ - ${{ parameters.HelixProjectArguments }} \ - /t:Test \ - /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog - env: - BuildConfig: $(_BuildConfig) - HelixSource: ${{ parameters.HelixSource }} - HelixType: ${{ parameters.HelixType }} - HelixBuild: ${{ parameters.HelixBuild }} - HelixConfiguration: ${{ parameters.HelixConfiguration }} - HelixTargetQueues: ${{ parameters.HelixTargetQueues }} - HelixEntraScope: ${{ parameters.HelixEntraScope }} - HelixPreCommands: ${{ parameters.HelixPreCommands }} - HelixPostCommands: ${{ parameters.HelixPostCommands }} - WorkItemDirectory: ${{ parameters.WorkItemDirectory }} - WorkItemCommand: ${{ parameters.WorkItemCommand }} - WorkItemTimeout: ${{ parameters.WorkItemTimeout }} - CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} - XUnitProjects: ${{ parameters.XUnitProjects }} - XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} - XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} - XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} - XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} - IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} - DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} - DotNetCliVersion: ${{ parameters.DotNetCliVersion }} - WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} - HelixBaseUri: ${{ parameters.HelixBaseUri }} - Creator: ${{ parameters.Creator }} - HELIX_AZDO_ACCESSTOKEN: $(System.AccessToken) - condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT')) - continueOnError: ${{ parameters.continueOnError }} + Write-Host "##vso[task.setvariable variable=HelixEntraClientId]$env:servicePrincipalId" + Write-Host "##vso[task.setvariable variable=HelixEntraTenantId]$env:tenantId" + condition: ${{ parameters.condition }} - - ${{ if eq(parameters.HelixUseEntraAuthentication, false) }}: - - powershell: > - $(Build.SourcesDirectory)\eng\common\msbuild.ps1 - $(Build.SourcesDirectory)/${{ parameters.HelixProjectPath }} - /restore - /p:TreatWarningsAsErrors=false - /p:EnableHelixJobMonitor=${{ parameters.UseHelixMonitor }} - /p:HelixUseEntraAuthentication=${{ parameters.HelixUseEntraAuthentication }} - ${{ parameters.HelixProjectArguments }} - /t:Test - /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog - displayName: ${{ parameters.DisplayNamePrefix }} (Windows) - env: - BuildConfig: $(_BuildConfig) - HelixSource: ${{ parameters.HelixSource }} - HelixType: ${{ parameters.HelixType }} - HelixBuild: ${{ parameters.HelixBuild }} - HelixConfiguration: ${{ parameters.HelixConfiguration }} - HelixTargetQueues: ${{ parameters.HelixTargetQueues }} + - powershell: > + $(Build.SourcesDirectory)\eng\common\msbuild.ps1 + $(Build.SourcesDirectory)/${{ parameters.HelixProjectPath }} + /restore + /p:TreatWarningsAsErrors=false + /p:EnableHelixJobMonitor=${{ parameters.UseHelixMonitor }} + /p:HelixUseEntraAuthentication=${{ parameters.HelixUseEntraAuthentication }} + ${{ parameters.HelixProjectArguments }} + /t:Test + /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog + displayName: ${{ parameters.DisplayNamePrefix }} (Windows) + env: + BuildConfig: $(_BuildConfig) + HelixSource: ${{ parameters.HelixSource }} + HelixType: ${{ parameters.HelixType }} + HelixBuild: ${{ parameters.HelixBuild }} + HelixConfiguration: ${{ parameters.HelixConfiguration }} + HelixTargetQueues: ${{ parameters.HelixTargetQueues }} + ${{ if eq(parameters.HelixUseEntraAuthentication, false) }}: HelixAccessToken: ${{ parameters.HelixAccessToken }} ${{ if eq(parameters.HelixUseEntraAuthentication, true) }}: AZURESUBSCRIPTION_CLIENT_ID: $(HelixEntraClientId) diff --git a/eng/common/run-helix-job-monitor.sh b/eng/common/run-helix-job-monitor.sh deleted file mode 100644 index c79bd98e3f7..00000000000 --- a/eng/common/run-helix-job-monitor.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -scriptroot="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -. "$scriptroot/pipeline-logging-functions.sh" - -toolArgs=( - --helix-base-uri "$HELIX_MONITOR_BASE_URI" - --use-entra-authentication "$HELIX_MONITOR_USE_ENTRA_AUTHENTICATION" - --helix-entra-scope "$HELIX_MONITOR_ENTRA_SCOPE" - --polling-interval-seconds "$HELIX_MONITOR_POLLING_INTERVAL_SECONDS" - --fail-on-failed-tests "$HELIX_MONITOR_FAIL_ON_FAILED_TESTS" - --allow-no-helix-jobs "$HELIX_MONITOR_ALLOW_NO_HELIX_JOBS" - --use-fully-qualified-test-name "$HELIX_MONITOR_USE_FULLY_QUALIFIED_TEST_NAME" - --max-wait-minutes "$((HELIX_MONITOR_TIMEOUT_IN_MINUTES - 5))" - --stage-name "$SYSTEM_STAGENAME" - --stage-attempt "$SYSTEM_STAGEATTEMPT" - --job-attempt "$SYSTEM_JOBATTEMPT" - --test-result-upload-parallelism "$HELIX_MONITOR_TEST_RESULT_UPLOAD_PARALLELISM" -) - -organization="${HELIX_MONITOR_ORGANIZATION:-}" -repository="${HELIX_MONITOR_REPOSITORY:-}" - -# Fall back to Azure DevOps-provided environment variables when the caller did not -# supply organization or repository explicitly. -if [ -z "$organization" ] || [ -z "$repository" ]; then - buildRepoName="${BUILD_REPOSITORY_NAME:-}" - if [ -n "$buildRepoName" ] && [[ "$buildRepoName" == */* ]]; then - repoOwner="${buildRepoName%%/*}" - repoName="${buildRepoName#*/}" - elif [ -n "$buildRepoName" ] && [[ "$buildRepoName" == *-* ]]; then - repoOwner="${buildRepoName%%-*}" - repoName="${buildRepoName#*-}" - fi - - if [ -n "${repoOwner:-}" ] && [ -n "${repoName:-}" ]; then - if [ -z "$organization" ]; then organization="$repoOwner"; fi - if [ -z "$repository" ]; then repository="$repoName"; fi - fi -fi - -if [ -n "$organization" ]; then toolArgs+=( --organization "$organization" ); fi -if [ -n "$repository" ]; then toolArgs+=( --repository "$repository" ); fi -if [ -n "${HELIX_MONITOR_TEST_RESULT_ATTACHMENT_MODE:-}" ]; then - toolArgs+=( --test-result-attachment-mode "$HELIX_MONITOR_TEST_RESULT_ATTACHMENT_MODE" ) -fi - -# These values let the monitor derive the same Helix source filter as the SDK submitter. -toolArgs+=( --build-reason "$BUILD_REASON" ) -toolArgs+=( --source-branch "$BUILD_SOURCEBRANCH" ) - -cd "$BUILD_SOURCESDIRECTORY" -exitCode=0 -if [ -n "${HELIX_MONITOR_TOOL_NUPKG_ARTIFACT_NAME:-}" ]; then - export DOTNET_ROOT="$BUILD_SOURCESDIRECTORY/.dotnet" - ./eng/common/dotnet.sh exec "$HELIXJOBMONITORDLL" "${toolArgs[@]}" || exitCode=$? -else - ./eng/common/dotnet.sh tool run "$HELIX_MONITOR_TOOL_COMMAND" -- "${toolArgs[@]}" || exitCode=$? -fi - -if [ "$exitCode" -ne 0 ]; then - Write-PipelineTelemetryError -force -category 'Helix' "Helix job monitor failed (exit code '$exitCode')." - exit "$exitCode" -fi From 081dd52acd18d9ad991e6f2aafb2eaa761db471b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2026 12:40:26 -0700 Subject: [PATCH 11/11] [release/11.0]: Bump Microsoft.Testing.Extensions.CodeCoverage from 18.11.2 to 18.12.0 (#39213) * [release/11.0]: Bump Microsoft.Testing.Extensions.CodeCoverage from 18.11.2 to 18.12.0 --- updated-dependencies: - dependency-name: Microsoft.Testing.Extensions.CodeCoverage dependency-version: 18.12.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Update MicrosoftTestingPlatformVersion to 2.5.0 Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --------- Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com --- eng/Versions.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index 105cc5c5b67..d70a459226d 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -46,8 +46,8 @@ 2.4.0 4.0.1 11.0.0-beta.26509.3 - 2.4.1 - 18.11.2 + 2.5.1 + 18.12.0