Skip to content

Commit 02f3dc2

Browse files
[nightly] Update common Docker engineering infrastructure with latest (#7201)
1 parent 207fa91 commit 02f3dc2

7 files changed

Lines changed: 353 additions & 5 deletions

File tree

eng/docker-tools/skill-helpers/AzureDevOps.ps1

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ function Invoke-AzDORestMethod {
4141
Request body as a hashtable. Automatically converted to JSON.
4242
.PARAMETER ApiVersion
4343
API version. Defaults to 7.1.
44+
.PARAMETER QueryParams
45+
Optional hashtable of additional query string parameters.
4446
#>
4547
[CmdletBinding()]
4648
param(
@@ -49,7 +51,8 @@ function Invoke-AzDORestMethod {
4951
[Parameter(Mandatory)][string] $Endpoint,
5052
[string] $Method = "GET",
5153
[hashtable] $Body,
52-
[string] $ApiVersion = "7.1"
54+
[string] $ApiVersion = "7.1",
55+
[hashtable] $QueryParams
5356
)
5457

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

61-
$uri = "https://dev.azure.com/$Organization/$Project/_apis/$($Endpoint)?api-version=$ApiVersion"
64+
$query = "api-version=$ApiVersion"
65+
if ($QueryParams) {
66+
foreach ($key in $QueryParams.Keys) {
67+
$value = [System.Uri]::EscapeDataString([string]$QueryParams[$key])
68+
$query += "&$key=$value"
69+
}
70+
}
71+
72+
$uri = "https://dev.azure.com/$Organization/$Project/_apis/$($Endpoint)?$query"
6273

6374
$params = @{
6475
Uri = $uri
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env pwsh
2+
# Lists pipeline definitions in a folder whose most recent completed build did not succeed.
3+
# Usage:
4+
# ./Get-FailingPipelines.ps1 -Organization dnceng -Project internal -Folder dotnet/docker-tools
5+
6+
[CmdletBinding()]
7+
param(
8+
[Parameter(Mandatory)][string] $Organization,
9+
[Parameter(Mandatory)][string] $Project,
10+
[Parameter(Mandatory)][string] $Folder,
11+
[switch] $IncludeWarnings
12+
)
13+
14+
$ErrorActionPreference = "Stop"
15+
16+
. "$PSScriptRoot/AzureDevOps.ps1"
17+
18+
# Normalize folder: accept "dotnet/docker-tools" or "\dotnet\docker-tools".
19+
$normalizedFolder = "\" + ($Folder.Trim('\', '/') -replace '/', '\')
20+
21+
$failingResults = @("failed", "canceled")
22+
if ($IncludeWarnings) {
23+
$failingResults += "partiallySucceeded"
24+
}
25+
26+
$definitions = Invoke-AzDORestMethod `
27+
-Organization $Organization `
28+
-Project $Project `
29+
-Endpoint "build/definitions" `
30+
-QueryParams @{
31+
path = $normalizedFolder
32+
includeLatestBuilds = "true"
33+
}
34+
35+
$failing = @()
36+
foreach ($def in $definitions.value) {
37+
$latest = $def.latestCompletedBuild
38+
if (-not $latest) { continue }
39+
if ($failingResults -notcontains $latest.result) { continue }
40+
41+
$failing += [pscustomobject]@{
42+
Definition = $def.name
43+
Result = $latest.result
44+
BuildId = $latest.id
45+
BuildNumber = $latest.buildNumber
46+
Branch = $latest.sourceBranch
47+
FinishTime = $latest.finishTime
48+
Url = "https://dev.azure.com/$Organization/$Project/_build/results?buildId=$($latest.id)"
49+
}
50+
}
51+
52+
Write-Host "## Failing pipelines in $normalizedFolder"
53+
Write-Host ""
54+
Write-Host "Found $($failing.Count) of $($definitions.value.Count) pipeline(s) with a failing latest run."
55+
Write-Host ""
56+
57+
if ($failing.Count -gt 0) {
58+
Write-Host "Pipeline | Result | Build | Branch | Finished | Link"
59+
Write-Host "--- | --- | --- | --- | --- | ---"
60+
foreach ($item in $failing | Sort-Object Definition) {
61+
Write-Host "$($item.Definition) | $($item.Result) | $($item.BuildId) | $($item.Branch) | $($item.FinishTime) | $($item.Url)"
62+
}
63+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/bin/env pwsh
2+
# Lists all build runs in the last N hours for pipelines under a given folder.
3+
# Usage:
4+
# ./Get-RecentBuilds.ps1 -Organization dnceng -Project internal -Folder dotnet/docker-tools
5+
# ./Get-RecentBuilds.ps1 -Organization dnceng -Project internal -Folder dotnet/docker-tools -Hours 48
6+
7+
[CmdletBinding()]
8+
param(
9+
[Parameter(Mandatory)][string] $Organization,
10+
[Parameter(Mandatory)][string] $Project,
11+
[Parameter(Mandatory)][string] $Folder,
12+
[int] $Hours = 24
13+
)
14+
15+
$ErrorActionPreference = "Stop"
16+
17+
. "$PSScriptRoot/AzureDevOps.ps1"
18+
19+
$normalizedFolder = "\" + ($Folder.Trim('\', '/') -replace '/', '\')
20+
$minTime = [DateTime]::UtcNow.AddHours(-$Hours).ToString("o")
21+
22+
$definitions = Invoke-AzDORestMethod `
23+
-Organization $Organization `
24+
-Project $Project `
25+
-Endpoint "build/definitions" `
26+
-QueryParams @{ path = $normalizedFolder }
27+
28+
if (-not $definitions.value -or $definitions.value.Count -eq 0) {
29+
Write-Host "## No pipelines found in $normalizedFolder"
30+
return
31+
}
32+
33+
$definitionIds = ($definitions.value | ForEach-Object { $_.id }) -join ","
34+
35+
$builds = Invoke-AzDORestMethod `
36+
-Organization $Organization `
37+
-Project $Project `
38+
-Endpoint "build/builds" `
39+
-QueryParams @{
40+
definitions = $definitionIds
41+
minTime = $minTime
42+
queryOrder = "finishTimeDescending"
43+
}
44+
45+
Write-Host "## Builds in $normalizedFolder (last $Hours hours)"
46+
Write-Host ""
47+
Write-Host "Found $($builds.value.Count) build(s) across $($definitions.value.Count) pipeline(s)."
48+
Write-Host ""
49+
50+
if ($builds.value.Count -gt 0) {
51+
Write-Host "Pipeline | State | Build | Branch | Finished | Link"
52+
Write-Host "--- | --- | --- | --- | --- | ---"
53+
foreach ($build in $builds.value) {
54+
$state = if ($build.status -eq "completed") { $build.result } else { $build.status }
55+
$url = "https://dev.azure.com/$Organization/$Project/_build/results?buildId=$($build.id)"
56+
Write-Host "$($build.definition.name) | $state | $($build.id) | $($build.sourceBranch) | $($build.finishTime) | $url"
57+
}
58+
}

eng/docker-tools/skill-helpers/Show-BuildTimeline.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ $build = Invoke-AzDORestMethod `
2121
-Project $Project `
2222
-Endpoint "build/builds/$BuildId"
2323

24-
Write-Host "# Build $BuildId - $($build.definition.name)"
24+
Write-Host "## Build $BuildId - $($build.definition.name)"
2525
Write-Host ""
2626
Write-Host "- Status: $($build.status) $(if ($build.result) { "($($build.result))" })"
2727
Write-Host "- Branch: $($build.sourceBranch)"
@@ -71,7 +71,7 @@ function Write-TimelineNode([string] $nodeId, [int] $depth) {
7171
}
7272
}
7373

74-
Write-Host "## Build Timeline"
74+
Write-Host "### Build Timeline"
7575
Write-Host ""
7676
Write-TimelineNode "" 0
7777
Write-Host ""
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#!/usr/bin/env pwsh
2+
# Shows all PR checks as a summary table, then expands AzDO build timelines for any
3+
# checks that point at Azure Pipelines (https://dev.azure.com/...).
4+
# Requires `gh` CLI authenticated against the target repo.
5+
#
6+
# Usage:
7+
# ./Show-PullRequestBuilds.ps1 -PullRequest 2100
8+
# ./Show-PullRequestBuilds.ps1 -PullRequest 2100 -Repo dotnet/docker-tools
9+
# ./Show-PullRequestBuilds.ps1 -PullRequest 2100 -ShowAllTasks
10+
11+
[CmdletBinding()]
12+
param(
13+
[Parameter(Mandatory)][int] $PullRequest,
14+
[string] $Repo,
15+
[switch] $ShowAllTasks
16+
)
17+
18+
$ErrorActionPreference = "Stop"
19+
20+
$ghArgs = @("pr", "view", $PullRequest, "--json", "statusCheckRollup")
21+
if ($Repo) { $ghArgs += @("--repo", $Repo) }
22+
23+
$checksJson = & gh @ghArgs 2>&1
24+
if ($LASTEXITCODE -ne 0) {
25+
throw "gh pr view failed: $checksJson"
26+
}
27+
28+
$checks = ($checksJson | ConvertFrom-Json).statusCheckRollup
29+
30+
# statusCheckRollup mixes two shapes:
31+
# CheckRun: { name, status, conclusion, detailsUrl, workflowName }
32+
# StatusContext: { context, state, targetUrl, description }
33+
# Normalize them.
34+
$normalized = foreach ($check in $checks) {
35+
if ($check.PSObject.Properties.Name -contains "context") {
36+
[pscustomobject]@{
37+
Name = $check.context
38+
State = $check.state
39+
Url = $check.targetUrl
40+
}
41+
}
42+
else {
43+
$state = if ($check.conclusion) { $check.conclusion } else { $check.status }
44+
[pscustomobject]@{
45+
Name = $check.name
46+
State = $state
47+
Url = $check.detailsUrl
48+
}
49+
}
50+
}
51+
52+
# AzDO build results URLs look like:
53+
# https://dev.azure.com/<org>/<project>/_build/results?buildId=<id>...
54+
$pattern = '^https?://dev\.azure\.com/(?<org>[^/]+)/(?<project>[^/]+)/_build/results\?.*buildId=(?<buildId>\d+)'
55+
56+
$builds = @()
57+
foreach ($check in $normalized) {
58+
if (-not $check.Url) { continue }
59+
$match = [regex]::Match($check.Url, $pattern)
60+
if (-not $match.Success) { continue }
61+
62+
$builds += [pscustomobject]@{
63+
Org = $match.Groups["org"].Value
64+
Project = $match.Groups["project"].Value
65+
BuildId = [int]$match.Groups["buildId"].Value
66+
}
67+
}
68+
69+
# Deduplicate by buildId (a single build can produce multiple check-run rows).
70+
$builds = $builds | Sort-Object BuildId -Unique
71+
72+
$title = if ($Repo) { "$Repo#$PullRequest" } else { "PR #$PullRequest" }
73+
Write-Host "## Checks for $title"
74+
Write-Host ""
75+
Write-Host "$($normalized.Count) check(s); $($builds.Count) Azure Pipelines build(s)."
76+
Write-Host ""
77+
78+
if ($normalized.Count -gt 0) {
79+
Write-Host "Check | State | URL"
80+
Write-Host "--- | --- | ---"
81+
foreach ($check in $normalized | Sort-Object Name) {
82+
Write-Host "$($check.Name) | $($check.State) | $($check.Url)"
83+
}
84+
Write-Host ""
85+
}
86+
87+
if ($builds.Count -eq 0) { return }
88+
89+
$timelineScript = "$PSScriptRoot/Show-BuildTimeline.ps1"
90+
91+
foreach ($build in $builds) {
92+
Write-Host "---"
93+
Write-Host ""
94+
& $timelineScript `
95+
-Organization $build.Org `
96+
-Project $build.Project `
97+
-BuildId $build.BuildId `
98+
-ShowAllTasks:$ShowAllTasks
99+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/usr/bin/env pwsh
2+
# Shows a focused summary of a pull request: metadata, reviews, issue-level comments,
3+
# and inline review comments (which `gh pr view --json` does not expose).
4+
# Requires `gh` CLI authenticated against the target repo.
5+
#
6+
# Usage:
7+
# ./Show-PullRequestComments.ps1 2100
8+
# ./Show-PullRequestComments.ps1 2100 -Repo dotnet/docker-tools
9+
10+
[CmdletBinding()]
11+
param(
12+
[Parameter(Mandatory)][int] $PullRequest,
13+
[string] $Repo
14+
)
15+
16+
$ErrorActionPreference = "Stop"
17+
$PSNativeCommandUseErrorActionPreference = $true
18+
19+
function Write-BlockComment {
20+
param([string] $Text)
21+
if (-not $Text) { return }
22+
$Text.TrimEnd() -split "`n" | ForEach-Object { Write-Host "> $_" }
23+
Write-Host ""
24+
}
25+
26+
# Fetch PR overview.
27+
$viewArgs = @(
28+
"pr", "view", $PullRequest,
29+
"--json", "number,title,state,author,baseRefName,headRefName,isDraft,url,additions,deletions,changedFiles,reviewDecision,labels,reviews,comments"
30+
)
31+
if ($Repo) { $viewArgs += @("--repo", $Repo) }
32+
33+
$prJson = & gh @viewArgs
34+
$pr = $prJson | ConvertFrom-Json
35+
36+
# Fetch inline review comments via REST API. `gh pr view --json` exposes review bodies
37+
# but drops the inline diff comments attached to specific files/lines.
38+
$apiPath = if ($Repo) {
39+
"repos/$Repo/pulls/$PullRequest/comments"
40+
} else {
41+
"repos/{owner}/{repo}/pulls/$PullRequest/comments"
42+
}
43+
44+
$inlineJson = & gh api --paginate $apiPath
45+
$inline = $inlineJson | ConvertFrom-Json
46+
47+
# Render.
48+
$title = if ($Repo) { "$Repo#$PullRequest" } else { "PR #$PullRequest" }
49+
Write-Host "## $title - $($pr.title)"
50+
Write-Host ""
51+
Write-Host "- State: $($pr.state)$(if ($pr.isDraft) { ' (draft)' })"
52+
Write-Host "- Author: $($pr.author.login)"
53+
Write-Host "- Branch: $($pr.headRefName) -> $($pr.baseRefName)"
54+
Write-Host "- Changes: +$($pr.additions)/-$($pr.deletions) ($($pr.changedFiles) files)"
55+
Write-Host "- Review decision: $($pr.reviewDecision)"
56+
if ($pr.labels) {
57+
Write-Host "- Labels: $(($pr.labels | ForEach-Object { $_.name }) -join ', ')"
58+
}
59+
Write-Host "- URL: $($pr.url)"
60+
Write-Host ""
61+
62+
# Conversation: top-level issue comments and review submissions, merged in
63+
# chronological order.
64+
$conversation = @()
65+
foreach ($comment in $pr.comments) {
66+
$conversation += [pscustomobject]@{
67+
Timestamp = [datetime]$comment.createdAt
68+
Header = "**$($comment.author.login)** commented at $($comment.createdAt):"
69+
Body = $comment.body
70+
}
71+
}
72+
foreach ($review in $pr.reviews) {
73+
$header = if ($review.state -eq "COMMENTED") {
74+
"**$($review.author.login)** left a comment at $($review.submittedAt):"
75+
} else {
76+
"**$($review.author.login)** reviewed ($($review.state)) at $($review.submittedAt):"
77+
}
78+
$conversation += [pscustomobject]@{
79+
Timestamp = [datetime]$review.submittedAt
80+
Header = $header
81+
Body = $review.body
82+
}
83+
}
84+
$conversation = $conversation | Sort-Object Timestamp
85+
86+
Write-Host "### Conversation ($($conversation.Count))"
87+
Write-Host ""
88+
if ($conversation.Count -eq 0) {
89+
Write-Host "_None_"
90+
} else {
91+
foreach ($entry in $conversation) {
92+
Write-Host $entry.Header
93+
Write-BlockComment $entry.Body
94+
}
95+
}
96+
Write-Host ""
97+
98+
# Inline review comments grouped by file and line.
99+
Write-Host "### Code review comments ($($inline.Count))"
100+
Write-Host ""
101+
if ($inline.Count -eq 0) {
102+
Write-Host "_None_"
103+
} else {
104+
$grouped = $inline |
105+
Group-Object -Property { "$($_.path):$(if ($_.line) { $_.line } else { $_.original_line })" } |
106+
Sort-Object Name
107+
foreach ($group in $grouped) {
108+
$first = $group.Group[0]
109+
$line = if ($first.line) { $first.line } else { $first.original_line }
110+
Write-Host "#### $($first.path) (line $line)"
111+
Write-Host ""
112+
foreach ($comment in $group.Group | Sort-Object created_at) {
113+
Write-Host "**$($comment.user.login)** commented at $($comment.created_at):"
114+
Write-BlockComment $comment.body
115+
}
116+
}
117+
}

0 commit comments

Comments
 (0)