Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion NuGet.config
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<clear />
<!--Begin: Package sources managed by Dependency Flow automation. Do not edit the sources below.-->
<!-- Begin: Package sources from dotnet-dotnet -->
<add key="darc-pub-dotnet-dotnet-493580a" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-dotnet-493580a5/nuget/v3/index.json" />
<add key="darc-pub-dotnet-dotnet-11741c8" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-dotnet-11741c82/nuget/v3/index.json" />
<!-- End: Package sources from dotnet-dotnet -->
<!--End: Package sources managed by Dependency Flow automation. Do not edit the sources above.-->
<add key="dotnet-public" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json" />
Expand Down
6 changes: 3 additions & 3 deletions eng/Version.Details.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ This file should be imported by eng/Versions.props
<Project>
<PropertyGroup>
<!-- dotnet-dotnet dependencies -->
<MicrosoftDotNetArcadeSdkPackageVersion>10.0.0-beta.26412.103</MicrosoftDotNetArcadeSdkPackageVersion>
<SystemCommandLinePackageVersion>2.0.12</SystemCommandLinePackageVersion>
<SystemSecurityCryptographyXmlPackageVersion>10.0.12</SystemSecurityCryptographyXmlPackageVersion>
<MicrosoftDotNetArcadeSdkPackageVersion>10.0.0-beta.26454.107</MicrosoftDotNetArcadeSdkPackageVersion>
<SystemCommandLinePackageVersion>2.0.13</SystemCommandLinePackageVersion>
<SystemSecurityCryptographyXmlPackageVersion>10.0.13</SystemSecurityCryptographyXmlPackageVersion>
</PropertyGroup>
<!--Property group for alternate package version names-->
<PropertyGroup>
Expand Down
14 changes: 7 additions & 7 deletions eng/Version.Details.xml
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Dependencies>
<Source Uri="https://github.com/dotnet/dotnet" Mapping="sourcelink" Sha="493580a515a01970cfe1da2c7dd589efbaf36996" BarId="326844" />
<Source Uri="https://github.com/dotnet/dotnet" Mapping="sourcelink" Sha="11741c82dbdf7acebca70db556911222226bee52" BarId="330600" />
<ProductDependencies>
<Dependency Name="System.CommandLine" Version="2.0.12">
<Dependency Name="System.CommandLine" Version="2.0.13">
<Uri>https://github.com/dotnet/dotnet</Uri>
<Sha>493580a515a01970cfe1da2c7dd589efbaf36996</Sha>
<Sha>11741c82dbdf7acebca70db556911222226bee52</Sha>
</Dependency>
<Dependency Name="System.Security.Cryptography.Xml" Version="10.0.12">
<Dependency Name="System.Security.Cryptography.Xml" Version="10.0.13">
<Uri>https://github.com/dotnet/dotnet</Uri>
<Sha>493580a515a01970cfe1da2c7dd589efbaf36996</Sha>
<Sha>11741c82dbdf7acebca70db556911222226bee52</Sha>
</Dependency>
</ProductDependencies>
<ToolsetDependencies>
<Dependency Name="Microsoft.DotNet.Arcade.Sdk" Version="10.0.0-beta.26412.103">
<Dependency Name="Microsoft.DotNet.Arcade.Sdk" Version="10.0.0-beta.26454.107">
<Uri>https://github.com/dotnet/dotnet</Uri>
<Sha>493580a515a01970cfe1da2c7dd589efbaf36996</Sha>
<Sha>11741c82dbdf7acebca70db556911222226bee52</Sha>
</Dependency>
</ToolsetDependencies>
</Dependencies>
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<Import Project="Version.Details.props" />

<PropertyGroup>
<VersionPrefix>10.0.112</VersionPrefix>
<VersionPrefix>10.0.113</VersionPrefix>
<PreReleaseVersionLabel>servicing</PreReleaseVersionLabel>
<PreReleaseVersionIteration></PreReleaseVersionIteration>
<!-- Allowed values: '', 'prerelease', 'release'. Set to 'release' when stabilizing. -->
Expand Down
25 changes: 18 additions & 7 deletions eng/common/Get-GitHubAppToken.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -110,27 +110,38 @@ $headers = @{

Write-Host "Looking up installation for '$InstallationOwner'..."
try {
$installations = @()
$installations = [System.Collections.Generic.List[object]]::new()
$page = 1
do {
$pageInstallations = @(Invoke-RestMethod `
$pageResponse = Invoke-RestMethod `
-Uri "https://api.github.com/app/installations?per_page=100&page=$page" `
-Headers $headers `
-Method Get)
$installations += $pageInstallations
-Method Get
$pageInstallationCount = 0
foreach ($installation in $pageResponse) {
$installations.Add($installation)
$pageInstallationCount++
}
$page++
} while ($pageInstallations.Count -eq 100)
} 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."
exit 1
}
$installation = $installations | Where-Object { $_.account.login -ieq $InstallationOwner } | Select-Object -First 1
if (-not $installation) {
$matchingInstallations = @($installations | Where-Object { $_.account.login -ieq $InstallationOwner })
if ($matchingInstallations.Count -eq 0) {
$found = ($installations | ForEach-Object { $_.account.login }) -join ', '
Write-PipelineTelemetryError -Category 'Build' -Message "No installation found for '$InstallationOwner'. App is installed on: $found"
exit 1
}
if ($matchingInstallations.Count -ne 1) {
$matchingIds = ($matchingInstallations | ForEach-Object { $_.id }) -join ', '
Write-PipelineTelemetryError -Category 'Build' -Message "Found multiple installations for '$InstallationOwner': $matchingIds"
exit 1
}
$installation = $matchingInstallations[0]
Write-Host "Using installation $($installation.id) for '$($installation.account.login)'."

try {
$tokenResponse = Invoke-RestMethod `
Expand Down
278 changes: 278 additions & 0 deletions eng/common/core-templates/job/helix-job-monitor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
parameters:
# Maximum run time of the monitor job in minutes. Also used for --max-wait-minutes.
- name: timeoutInMinutes
type: number
default: 360

# Owner segment of the source repository (e.g. 'dotnet' for 'dotnet/runtime') passed via --organization.
# Defaults to the owner segment of BUILD_REPOSITORY_NAME when empty.
- name: organization
type: string
default: ''

# Name of the source repository (e.g. 'runtime' for 'dotnet/runtime') passed via --repository.
# Defaults to the repo segment of BUILD_REPOSITORY_NAME when empty.
- name: repository
type: string
default: ''

# Optional dependency list for the generated job.
- name: dependsOn
type: object
default: []

# Optional condition for the generated job.
- name: condition
type: string
default: ''

# Whether failures in the monitor job should allow the pipeline to continue.
- name: continueOnError
type: boolean
default: false

# NuGet package id of the Helix job monitor tool.
- name: toolPackageId
type: string
default: Microsoft.DotNet.Helix.JobMonitor

# Console command exposed by the installed tool package.
- name: toolCommand
type: string
default: dotnet-helix-job-monitor

# Optional explicit tool version. Only honored when 'toolNupkgArtifactName' is set; in the
# default code path the version is taken from the consuming repo's .config/dotnet-tools.json.
- name: toolVersion
type: string
default: ''

# Base URI for the Helix service (--helix-base-uri).
- name: helixBaseUri
type: string
default: https://helix.dot.net/

# Helix API access token forwarded to the tool via the HELIX_ACCESSTOKEN environment variable.
- name: helixAccessToken
type: string
default: ''

# Polling interval in seconds (--polling-interval-seconds).
- name: pollingIntervalSeconds
type: number
default: 30

# Maximum number of work items whose results may be downloaded, parsed, and
# uploaded concurrently.
- name: testResultUploadParallelism
type: number
default: 48

# When 'true' (the default), Helix work items that exit 0 but have failed AzDO test results
# are treated as failed: they count toward the monitor's exit code and are resubmitted by a
# later invocation's retry pass. Set to 'false' to fall back to exit-code-only outcomes.
# Forwarded as --fail-on-failed-tests.
- name: failWorkItemsWithFailedTests
type: boolean
default: true

# When true, allow the monitor to succeed when this stage produces no Helix jobs in any attempt.
# Forwarded as --allow-no-helix-jobs.
- name: allowNoHelixJobs
type: boolean
default: false

# When true, test results are reported to Azure DevOps using the fully qualified test name
# (Namespace.Type.Method) as the stable automatedTestName and the visible title is qualified as
# well (--use-fully-qualified-test-name). Opt-in because it changes AzDO test identity and display;
# primarily useful for frameworks like MSTest whose display name is only the method name.
- name: useFullyQualifiedTestName
type: boolean
default: false

# Controls per-test output attachments. Defaults to Failed.
- name: testResultAttachmentMode
type: string
default: Failed
values:
- Failed
- All
- None

# Advanced: optional pipeline artifact (produced earlier in this run) that contains the tool
# nupkg. When set, the artifact is downloaded and the tool is installed from the nupkg into
# a local tool-path; this bypasses the repo's .config/dotnet-tools.json manifest and is
# primarily intended for the Arcade repository itself, where the Helix job monitor tool is
# built in the same pipeline that runs this template.
#
# When this parameter is empty (the default), the consuming repository must declare the tool
# in its .config/dotnet-tools.json manifest (alongside other local .NET tools); the template
# will check out the repo and run 'dotnet tool restore' to install the version pinned there.
- name: toolNupkgArtifactName
type: string
default: ''

# Advanced: sub-path within the downloaded artifact where the tool nupkg is located. Defaults
# to the standard Arcade non-shipping packages location for a Release build (relative to the
# pipeline artifact root, which is itself the build's 'artifacts' directory).
- name: toolNupkgArtifactSubPath
type: string
default: 'packages/Release/NonShipping'

jobs:
- job: HelixJobMonitor
displayName: Monitor Helix Jobs
timeoutInMinutes: ${{ parameters.timeoutInMinutes }}
continueOnError: ${{ parameters.continueOnError }}
${{ if ne(length(parameters.dependsOn), 0) }}:
dependsOn: ${{ parameters.dependsOn }}
${{ if ne(parameters.condition, '') }}:
condition: ${{ parameters.condition }}
pool:
${{ if eq(variables['System.TeamProject'], 'public') }}:
name: $(DncEngPublicBuildPool)
os: linux
demands: ImageOverride -equals build.azurelinux.3.amd64.open
${{ else }}:
name: $(DncEngInternalBuildPool)
os: linux
demands: ImageOverride -equals build.azurelinux.3.amd64
steps:
- checkout: self
fetchDepth: 1

- ${{ if ne(parameters.toolNupkgArtifactName, '') }}:
- task: DownloadPipelineArtifact@2
displayName: Download Helix Job Monitor artifact
inputs:
buildType: current
artifactName: ${{ parameters.toolNupkgArtifactName }}
itemPattern: '${{ parameters.toolNupkgArtifactSubPath }}/${{ parameters.toolPackageId }}.*.nupkg'
targetPath: $(Agent.TempDirectory)/helix-job-monitor-nupkg

- bash: |
set -euo pipefail

toolPath="$AGENT_TEMPDIRECTORY/helix-job-monitor-tool"
mkdir -p "$toolPath"

packageId='${{ parameters.toolPackageId }}'
toolVersion='${{ parameters.toolVersion }}'
nupkgArtifactSubPath='${{ parameters.toolNupkgArtifactSubPath }}'
nupkgDir="$AGENT_TEMPDIRECTORY/helix-job-monitor-nupkg/$nupkgArtifactSubPath"

if [ ! -d "$nupkgDir" ]; then
echo "Expected nupkg directory '$nupkgDir' was not produced by the artifact download." >&2
exit 1
fi

nupkg=$(find "$nupkgDir" -maxdepth 1 -type f -name "$packageId.*.nupkg" | head -n 1)
if [ -z "$nupkg" ]; then
echo "No '$packageId.*.nupkg' found in '$nupkgDir'." >&2
exit 1
fi

# Derive the version from the nupkg filename so the local package is selected
# deterministically instead of resolving against any other configured feed.
nupkgBase=$(basename "$nupkg" .nupkg)
derivedVersion="${nupkgBase#${packageId}.}"
if [ -z "$toolVersion" ]; then
toolVersion="$derivedVersion"
fi

echo "Using locally built '$packageId' version '$toolVersion' from '$nupkgDir'."

# Create a minimal NuGet.config that only references the local nupkg directory.
# This avoids conflicts with the repo's package source mapping which blocks --add-source.
toolNugetConfig="$AGENT_TEMPDIRECTORY/helix-job-monitor-nuget.config"
printf '<?xml version="1.0" encoding="utf-8"?>\n<configuration>\n <packageSources>\n <clear />\n <add key="local-tool" value="%s" />\n </packageSources>\n</configuration>\n' "$nupkgDir" > "$toolNugetConfig"

pushd "$(Build.SourcesDirectory)" > /dev/null
./eng/common/dotnet.sh tool install \
--tool-path "$toolPath" "$packageId" \
--version "$toolVersion" \
--configfile "$toolNugetConfig"

# Locate the tool DLL so the run step can invoke it via ./eng/common/dotnet.sh exec.
toolDll=$(find "$toolPath/.store" -path '*/tools/*/any/*.deps.json' -type f | head -n 1)
toolDll="${toolDll%.deps.json}.dll"
if [ ! -f "$toolDll" ]; then
echo "Could not find tool DLL in '$toolPath/.store'." >&2
exit 1
fi

echo "Tool DLL: $toolDll"
echo "##vso[task.setvariable variable=HelixJobMonitorDll]$toolDll"
displayName: Install Helix Job Monitor

- ${{ else }}:
- bash: ./eng/common/dotnet.sh tool restore
displayName: Restore Helix Job Monitor

- bash: |
set -euo pipefail

toolArgs=(
--helix-base-uri '${{ parameters.helixBaseUri }}'
--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)
HELIX_ACCESSTOKEN: ${{ parameters.helixAccessToken }}
Loading
Loading