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
74 changes: 74 additions & 0 deletions eng/docker-tools/skill-helpers/AzureDevOps.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env pwsh
# Lightweight wrapper for authenticated Azure DevOps REST API calls.
# Uses `az account get-access-token` for bearer token auth.
#
# Usage:
# . ./AzureDevOps.ps1
# $response = Invoke-AzDORestMethod -Organization myorg -Project myproject `
# -Endpoint "pipelines/42/runs" -Method POST -Body @{ resources = @{} }

$ErrorActionPreference = "Stop"

function Get-AzDOAccessToken {
<#
.SYNOPSIS
Returns a bearer token for Azure DevOps.
#>

# Well-known Entra ID application ID for Azure DevOps
$tokenJson = az account get-access-token --resource "499b84ac-1321-427f-aa17-267ca6975798" 2>&1
if ($LASTEXITCODE -ne 0) {
throw "Failed to get access token. Run 'az login' first. Output: $tokenJson"
}

$parsed = $tokenJson | ConvertFrom-Json
return $parsed.accessToken
}

function Invoke-AzDORestMethod {
<#
.SYNOPSIS
Calls an Azure DevOps REST API endpoint with automatic authentication.
.PARAMETER Organization
Azure DevOps organization name (not the full URL).
.PARAMETER Project
Azure DevOps project name.
.PARAMETER Endpoint
API path after _apis/ (e.g. "pipelines/42/runs", "build/builds").
.PARAMETER Method
HTTP method. Defaults to GET.
.PARAMETER Body
Request body as a hashtable. Automatically converted to JSON.
.PARAMETER ApiVersion
API version. Defaults to 7.1.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)][string] $Organization,
[Parameter(Mandatory)][string] $Project,
[Parameter(Mandatory)][string] $Endpoint,
[string] $Method = "GET",
[hashtable] $Body,
[string] $ApiVersion = "7.1"
)

$token = Get-AzDOAccessToken
$headers = @{
Authorization = "Bearer $token"
"Content-Type" = "application/json"
}

$uri = "https://dev.azure.com/$Organization/$Project/_apis/$($Endpoint)?api-version=$ApiVersion"

$params = @{
Uri = $uri
Headers = $headers
Method = $Method
}

if ($Body) {
$params.Body = $Body | ConvertTo-Json -Depth 10
}

return Invoke-RestMethod @params
}
21 changes: 21 additions & 0 deletions eng/docker-tools/skill-helpers/Get-BuildLog.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env pwsh
# Retrieves a build log by log ID.
# Usage:
# ./Get-BuildLog.ps1 -Organization dnceng -Project internal -BuildId 12345 -LogId 47

[CmdletBinding()]
param(
[Parameter(Mandatory)][string] $Organization,
[Parameter(Mandatory)][string] $Project,
[Parameter(Mandatory)][int] $BuildId,
[Parameter(Mandatory)][int] $LogId
)

$ErrorActionPreference = "Stop"

. "$PSScriptRoot/AzureDevOps.ps1"

Invoke-AzDORestMethod `
-Organization $Organization `
-Project $Project `
-Endpoint "build/builds/$BuildId/logs/$LogId"
77 changes: 77 additions & 0 deletions eng/docker-tools/skill-helpers/Show-BuildTimeline.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/usr/bin/env pwsh
# Prints the build timeline as an indented tree with result indicators.
# Usage:
# ./Show-BuildTimeline.ps1 -Organization dnceng -Project internal -BuildId 12345
# ./Show-BuildTimeline.ps1 -Organization dnceng -Project internal -BuildId 12345 -ShowAllTasks

[CmdletBinding()]
param(
[Parameter(Mandatory)][string] $Organization,
[Parameter(Mandatory)][string] $Project,
[Parameter(Mandatory)][int] $BuildId,
[switch] $ShowAllTasks
)

$ErrorActionPreference = "Stop"

. "$PSScriptRoot/AzureDevOps.ps1"

$build = Invoke-AzDORestMethod `
-Organization $Organization `
-Project $Project `
-Endpoint "build/builds/$BuildId"

Write-Host "# Build $BuildId - $($build.definition.name)"
Write-Host ""
Write-Host "- Status: $($build.status) $(if ($build.result) { "($($build.result))" })"
Write-Host "- Branch: $($build.sourceBranch)"
Write-Host "- Queued: $($build.queueTime)"
Write-Host "- URL: $($build._links.web.href)"
Write-Host ""

$timeline = Invoke-AzDORestMethod `
-Organization $Organization `
-Project $Project `
-Endpoint "build/builds/$BuildId/timeline"

$records = $timeline.records

# Build a lookup of children grouped by parentId
$childrenOf = @{}
foreach ($record in $records) {
$parentId = $record.parentId
if (-not $parentId) { $parentId = "" }
if (-not $childrenOf.ContainsKey($parentId)) {
$childrenOf[$parentId] = [System.Collections.Generic.List[object]]::new()
}
$childrenOf[$parentId].Add($record)
}

# Sort children by order within each group
foreach ($key in @($childrenOf.Keys)) {
$childrenOf[$key] = $childrenOf[$key] | Sort-Object { $_.order }
}

function Write-TimelineNode([string] $nodeId, [int] $depth) {
$children = $childrenOf[$nodeId]
if (-not $children) { return }

foreach ($child in $children) {
$isTask = $child.type -eq "Task"
$isFailing = $child.result -in @("failed", "canceled", "abandoned") -or $child.state -eq "inProgress"
if ($isTask -and -not $ShowAllTasks -and -not $isFailing) { continue }

$indent = " " * $depth
$status = if ($child.result) { $child.result } else { $child.state }

$logId = $child.log.id
$logLabel = if ($logId) { " #$logId" } else { "" }
Write-Host "${indent}- $($child.type)$logLabel | $($child.name) | $status"
Write-TimelineNode $child.id ($depth + 1)
}
}

Write-Host "## Build Timeline"
Write-Host ""
Write-TimelineNode "" 0
Write-Host ""
5 changes: 4 additions & 1 deletion eng/docker-tools/templates/jobs/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,9 @@ jobs:
#
# https://github.com/dotnet/docker-tools/issues/1698 tracks making this command no longer depend
# on individual step displayNames.
#
# Skipped for PR builds because manifest lists are not created in PR builds (see post-build.yml).
# Without manifest lists present in image-info.json, the postPublishNotification fails with a NRE.
- script: >
$(runImageBuilderCmd) postPublishNotification
'$(publishNotificationRepoName)'
Expand All @@ -266,7 +269,7 @@ jobs:
$(dryRunArg)
$(imageBuilder.commonCmdArgs)
displayName: Post Publish Notification
condition: and(always(), eq(variables['publishNotificationsEnabled'], 'true'))
condition: and(always(), eq(variables['publishNotificationsEnabled'], 'true'), ne(variables['Build.Reason'], 'PullRequest'))
- powershell: |
# Default to current build number if parameter was not overridden
Expand Down
2 changes: 1 addition & 1 deletion eng/docker-tools/templates/variables/docker-images.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
variables:
imageNames.imageBuilderName: mcr.microsoft.com/dotnet-buildtools/image-builder:2936017
imageNames.imageBuilderName: mcr.microsoft.com/dotnet-buildtools/image-builder:2940448
imageNames.imageBuilder: $(imageNames.imageBuilderName)
imageNames.imageBuilder.withrepo: imagebuilder-withrepo:$(Build.BuildId)-$(System.JobId)
imageNames.testRunner: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux3.0-docker-testrunner
Expand Down
Loading