Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 0 deletions eng/docker-tools/DEV-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,8 @@ When you queue a new run, you can override these as runtime parameters:

This avoids the multi-hour rebuild cycle when you just need to retry a failed operation.

When signing is enabled, use `"publish"` by itself only if the images from `sourceBuildPipelineRunId` were already signed and the current run is not building new images. Use `"sign,publish"` when the current run still needs to sign them before publishing.

---

## Troubleshooting
Expand Down
15 changes: 13 additions & 2 deletions eng/docker-tools/skill-helpers/AzureDevOps.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ function Invoke-AzDORestMethod {
Request body as a hashtable. Automatically converted to JSON.
.PARAMETER ApiVersion
API version. Defaults to 7.1.
.PARAMETER QueryParams
Optional hashtable of additional query string parameters.
#>
[CmdletBinding()]
param(
Expand All @@ -49,7 +51,8 @@ function Invoke-AzDORestMethod {
[Parameter(Mandatory)][string] $Endpoint,
[string] $Method = "GET",
[hashtable] $Body,
[string] $ApiVersion = "7.1"
[string] $ApiVersion = "7.1",
[hashtable] $QueryParams
)

$token = Get-AzDOAccessToken
Expand All @@ -58,7 +61,15 @@ function Invoke-AzDORestMethod {
"Content-Type" = "application/json"
}

$uri = "https://dev.azure.com/$Organization/$Project/_apis/$($Endpoint)?api-version=$ApiVersion"
$query = "api-version=$ApiVersion"
if ($QueryParams) {
foreach ($key in $QueryParams.Keys) {
$value = [System.Uri]::EscapeDataString([string]$QueryParams[$key])
$query += "&$key=$value"
}
}

$uri = "https://dev.azure.com/$Organization/$Project/_apis/$($Endpoint)?$query"

$params = @{
Uri = $uri
Expand Down
63 changes: 63 additions & 0 deletions eng/docker-tools/skill-helpers/Get-FailingPipelines.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env pwsh
# Lists pipeline definitions in a folder whose most recent completed build did not succeed.
# Usage:
# ./Get-FailingPipelines.ps1 -Organization dnceng -Project internal -Folder dotnet/docker-tools

[CmdletBinding()]
param(
[Parameter(Mandatory)][string] $Organization,
[Parameter(Mandatory)][string] $Project,
[Parameter(Mandatory)][string] $Folder,
[switch] $IncludeWarnings
)

$ErrorActionPreference = "Stop"

. "$PSScriptRoot/AzureDevOps.ps1"

# Normalize folder: accept "dotnet/docker-tools" or "\dotnet\docker-tools".
$normalizedFolder = "\" + ($Folder.Trim('\', '/') -replace '/', '\')

$failingResults = @("failed", "canceled")
if ($IncludeWarnings) {
$failingResults += "partiallySucceeded"
}

$definitions = Invoke-AzDORestMethod `
-Organization $Organization `
-Project $Project `
-Endpoint "build/definitions" `
-QueryParams @{
path = $normalizedFolder
includeLatestBuilds = "true"
}

$failing = @()
foreach ($def in $definitions.value) {
$latest = $def.latestCompletedBuild
if (-not $latest) { continue }
if ($failingResults -notcontains $latest.result) { continue }

$failing += [pscustomobject]@{
Definition = $def.name
Result = $latest.result
BuildId = $latest.id
BuildNumber = $latest.buildNumber
Branch = $latest.sourceBranch
FinishTime = $latest.finishTime
Url = "https://dev.azure.com/$Organization/$Project/_build/results?buildId=$($latest.id)"
}
}

Write-Host "## Failing pipelines in $normalizedFolder"
Write-Host ""
Write-Host "Found $($failing.Count) of $($definitions.value.Count) pipeline(s) with a failing latest run."
Write-Host ""

if ($failing.Count -gt 0) {
Write-Host "Pipeline | Result | Build | Branch | Finished | Link"
Write-Host "--- | --- | --- | --- | --- | ---"
foreach ($item in $failing | Sort-Object Definition) {
Write-Host "$($item.Definition) | $($item.Result) | $($item.BuildId) | $($item.Branch) | $($item.FinishTime) | $($item.Url)"
}
}
58 changes: 58 additions & 0 deletions eng/docker-tools/skill-helpers/Get-RecentBuilds.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env pwsh
# Lists all build runs in the last N hours for pipelines under a given folder.
# Usage:
# ./Get-RecentBuilds.ps1 -Organization dnceng -Project internal -Folder dotnet/docker-tools
# ./Get-RecentBuilds.ps1 -Organization dnceng -Project internal -Folder dotnet/docker-tools -Hours 48

[CmdletBinding()]
param(
[Parameter(Mandatory)][string] $Organization,
[Parameter(Mandatory)][string] $Project,
[Parameter(Mandatory)][string] $Folder,
[int] $Hours = 24
)

$ErrorActionPreference = "Stop"

. "$PSScriptRoot/AzureDevOps.ps1"

$normalizedFolder = "\" + ($Folder.Trim('\', '/') -replace '/', '\')
$minTime = [DateTime]::UtcNow.AddHours(-$Hours).ToString("o")

$definitions = Invoke-AzDORestMethod `
-Organization $Organization `
-Project $Project `
-Endpoint "build/definitions" `
-QueryParams @{ path = $normalizedFolder }

if (-not $definitions.value -or $definitions.value.Count -eq 0) {
Write-Host "## No pipelines found in $normalizedFolder"
return
}

$definitionIds = ($definitions.value | ForEach-Object { $_.id }) -join ","

$builds = Invoke-AzDORestMethod `
-Organization $Organization `
-Project $Project `
-Endpoint "build/builds" `
-QueryParams @{
definitions = $definitionIds
minTime = $minTime
queryOrder = "finishTimeDescending"
}

Write-Host "## Builds in $normalizedFolder (last $Hours hours)"
Write-Host ""
Write-Host "Found $($builds.value.Count) build(s) across $($definitions.value.Count) pipeline(s)."
Write-Host ""

if ($builds.value.Count -gt 0) {
Write-Host "Pipeline | State | Build | Branch | Finished | Link"
Write-Host "--- | --- | --- | --- | --- | ---"
foreach ($build in $builds.value) {
$state = if ($build.status -eq "completed") { $build.result } else { $build.status }
$url = "https://dev.azure.com/$Organization/$Project/_build/results?buildId=$($build.id)"
Write-Host "$($build.definition.name) | $state | $($build.id) | $($build.sourceBranch) | $($build.finishTime) | $url"
}
}
4 changes: 2 additions & 2 deletions eng/docker-tools/skill-helpers/Show-BuildTimeline.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ $build = Invoke-AzDORestMethod `
-Project $Project `
-Endpoint "build/builds/$BuildId"

Write-Host "# Build $BuildId - $($build.definition.name)"
Write-Host "## Build $BuildId - $($build.definition.name)"
Write-Host ""
Write-Host "- Status: $($build.status) $(if ($build.result) { "($($build.result))" })"
Write-Host "- Branch: $($build.sourceBranch)"
Expand Down Expand Up @@ -71,7 +71,7 @@ function Write-TimelineNode([string] $nodeId, [int] $depth) {
}
}

Write-Host "## Build Timeline"
Write-Host "### Build Timeline"
Write-Host ""
Write-TimelineNode "" 0
Write-Host ""
99 changes: 99 additions & 0 deletions eng/docker-tools/skill-helpers/Show-PullRequestBuilds.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/usr/bin/env pwsh
# Shows all PR checks as a summary table, then expands AzDO build timelines for any
# checks that point at Azure Pipelines (https://dev.azure.com/...).
# Requires `gh` CLI authenticated against the target repo.
#
# Usage:
# ./Show-PullRequestBuilds.ps1 -PullRequest 2100
# ./Show-PullRequestBuilds.ps1 -PullRequest 2100 -Repo dotnet/docker-tools
# ./Show-PullRequestBuilds.ps1 -PullRequest 2100 -ShowAllTasks

[CmdletBinding()]
param(
[Parameter(Mandatory)][int] $PullRequest,
[string] $Repo,
[switch] $ShowAllTasks
)

$ErrorActionPreference = "Stop"

$ghArgs = @("pr", "view", $PullRequest, "--json", "statusCheckRollup")
if ($Repo) { $ghArgs += @("--repo", $Repo) }

$checksJson = & gh @ghArgs 2>&1
if ($LASTEXITCODE -ne 0) {
throw "gh pr view failed: $checksJson"
}

$checks = ($checksJson | ConvertFrom-Json).statusCheckRollup

# statusCheckRollup mixes two shapes:
# CheckRun: { name, status, conclusion, detailsUrl, workflowName }
# StatusContext: { context, state, targetUrl, description }
# Normalize them.
$normalized = foreach ($check in $checks) {
if ($check.PSObject.Properties.Name -contains "context") {
[pscustomobject]@{
Name = $check.context
State = $check.state
Url = $check.targetUrl
}
}
else {
$state = if ($check.conclusion) { $check.conclusion } else { $check.status }
[pscustomobject]@{
Name = $check.name
State = $state
Url = $check.detailsUrl
}
}
}

# AzDO build results URLs look like:
# https://dev.azure.com/<org>/<project>/_build/results?buildId=<id>...
$pattern = '^https?://dev\.azure\.com/(?<org>[^/]+)/(?<project>[^/]+)/_build/results\?.*buildId=(?<buildId>\d+)'

$builds = @()
foreach ($check in $normalized) {
if (-not $check.Url) { continue }
$match = [regex]::Match($check.Url, $pattern)
if (-not $match.Success) { continue }

$builds += [pscustomobject]@{
Org = $match.Groups["org"].Value
Project = $match.Groups["project"].Value
BuildId = [int]$match.Groups["buildId"].Value
}
}

# Deduplicate by buildId (a single build can produce multiple check-run rows).
$builds = $builds | Sort-Object BuildId -Unique

$title = if ($Repo) { "$Repo#$PullRequest" } else { "PR #$PullRequest" }
Write-Host "## Checks for $title"
Write-Host ""
Write-Host "$($normalized.Count) check(s); $($builds.Count) Azure Pipelines build(s)."
Write-Host ""

if ($normalized.Count -gt 0) {
Write-Host "Check | State | URL"
Write-Host "--- | --- | ---"
foreach ($check in $normalized | Sort-Object Name) {
Write-Host "$($check.Name) | $($check.State) | $($check.Url)"
}
Write-Host ""
}

if ($builds.Count -eq 0) { return }

$timelineScript = "$PSScriptRoot/Show-BuildTimeline.ps1"

foreach ($build in $builds) {
Write-Host "---"
Write-Host ""
& $timelineScript `
-Organization $build.Org `
-Project $build.Project `
-BuildId $build.BuildId `
-ShowAllTasks:$ShowAllTasks
}
Loading
Loading