vcpkg/.github/skills/analyze-ci-failures/references/azure-devops-api.md

272 lines
8.5 KiB
Markdown

# Azure DevOps REST API Reference for vcpkg CI
## Base URL
```
https://dev.azure.com/vcpkg/public/_apis
```
All endpoints below are appended to this base. The `vcpkg/public` project is **publicly readable** — no Authorization header is required.
## API Version
Always append `?api-version=7.0` (or include it in query strings).
---
## Build Endpoints
### Get Build Details
```
GET https://dev.azure.com/{org}/{project}/_apis/build/builds/{buildId}?api-version=7.0
```
**Response fields:**
```json
{
"id": 129315,
"buildNumber": "20240315.1",
"status": "completed",
"result": "failed",
"reason": "pullRequest",
"sourceBranch": "refs/pull/12345/merge",
"sourceVersion": "abc123def456...",
"requestedFor": { "displayName": "Jane Developer" },
"startTime": "2024-03-15T10:00:00Z",
"finishTime": "2024-03-15T14:30:00Z",
"definition": { "name": "vcpkg.CI" },
"_links": {
"web": { "href": "https://dev.azure.com/vcpkg/public/_build/results?buildId=129315" }
}
}
```
**`result` values:** `succeeded` | `failed` | `partiallySucceeded` | `canceled`
**`reason` values:** `pullRequest` | `schedule` | `manual` | `individualCI` | `batchedCI`
---
### Get Build Timeline
```
GET https://dev.azure.com/{org}/{project}/_apis/build/builds/{buildId}/timeline?api-version=7.0
```
Returns all stages, jobs, tasks and their results.
**Response structure:**
```json
{
"records": [
{
"id": "guid",
"parentId": "parent-guid-or-null",
"type": "Job",
"name": "x64_windows",
"displayName": "x64_windows",
"state": "completed",
"result": "failed",
"startTime": "2024-03-15T10:05:00Z",
"finishTime": "2024-03-15T12:15:00Z",
"log": {
"id": 42,
"type": "Container",
"url": "https://dev.azure.com/vcpkg/public/_apis/build/builds/129315/logs/42"
},
"errorCount": 1,
"warningCount": 0
}
]
}
```
**`type` values:** `Stage` | `Phase` | `Job` | `Task`
**`result` values:** `succeeded` | `failed` | `skipped` | `canceled` | `succeededWithIssues`
**To find failed jobs:**
```python
failed_jobs = [r for r in records if r['type'] == 'Job' and r['result'] == 'failed']
```
**Job names map to triplets** (underscores in job names, hyphens in triplets):
- `x64_windows` → `x64-windows`
- `x64_windows_static` → `x64-windows-static`
- `arm64_osx` → `arm64-osx`
- `x64_linux` → `x64-linux`
- `arm_neon_android` → `arm-neon-android`
---
### Get a Specific Log
```
GET https://dev.azure.com/{org}/{project}/_apis/build/builds/{buildId}/logs/{logId}?api-version=7.0
```
Returns plain text of the log. Use this to read the "*** Test Modified Ports" task log, which contains `REGRESSION:` lines for **all** failures including those without artifact logs (e.g., `FILE_CONFLICTS`).
**Finding the right log ID:** Look up the task record in the timeline response — the `log.id` field on the `"*** Test Modified Ports"` task record gives the log ID. Alternatively, use `log.url` directly.
**Scanning for REGRESSION lines:**
```powershell
$logText = Invoke-RestMethod "https://dev.azure.com/{org}/{project}/_apis/build/builds/{buildId}/logs/{logId}?api-version=7.0"
$logText -split "`n" | Where-Object { $_ -match '^REGRESSION:' }
```
**Example output:**
```
REGRESSION: kf6i18n:x64-windows failed with FILE_CONFLICTS. If expected, add kf6i18n:x64-windows=fail to .../ci.baseline.txt.
REGRESSION: kf6itemmodels:x64-windows failed with FILE_CONFLICTS. If expected, add kf6itemmodels:x64-windows=fail to .../ci.baseline.txt.
REGRESSION: v8:x64-windows failed with BUILD_FAILED. If expected, add v8:x64-windows=fail to .../ci.baseline.txt.
```
> ⚠️ **`FILE_CONFLICTS` failures do NOT produce failure log artifacts.** The only record of them is in this step log. Always scan all "Test Modified Ports" task logs before relying on artifacts alone.
### List All Logs
```
GET https://dev.azure.com/{org}/{project}/_apis/build/builds/{buildId}/logs?api-version=7.0
```
---
## Artifact Endpoints
### List All Artifacts
```
GET https://dev.azure.com/{org}/{project}/_apis/build/builds/{buildId}/artifacts?api-version=7.0
```
**Response:**
```json
{
"count": 39,
"value": [
{
"id": 1107682,
"name": "failure logs for x64-windows",
"source": "7922e5c4-...",
"resource": {
"type": "PipelineArtifact",
"data": "DCCD3D9C96CE79A758B255BD87397A07371B8394EE52F6BFF35346C144D63F3501",
"properties": {
"RootId": "196FDE3B...",
"artifactsize": "8586850",
"HashType": "DEDUP1024K",
"DomainId": "0"
},
"url": "https://dev.azure.com/vcpkg/.../_apis/build/builds/129315/artifacts?artifactName=failure%20logs%20for%20x64-windows&api-version=7.0",
"downloadUrl": "https://artprodwus21.artifacts.visualstudio.com/.../_apis/artifact/{base64hash}/content?format=zip"
}
}
]
}
```
> ⚠️ **`resource.type` is `"PipelineArtifact"`**, not `"Container"`. The Container listing API
> (`GET /_apis/resources/Containers/{id}`) does **not** work for these artifacts.
> Use `resource.downloadUrl` directly to download the ZIP.
**Artifact naming conventions:**
| Artifact Name Pattern | Contents | Priority |
|---|---|---|
| `failure logs for {triplet}` | Per-port build failure log directories | **High** — primary source |
| `file lists for {triplet}` | Installed file manifests | Low — skip unless needed |
| `format.diff` | Formatting/manifest diff | **Medium** — indicates PR format issues |
| `z azcopy logs for {triplet}` | Binary cache transfer logs | Low — infrastructure only |
---
### Download Artifact ZIP (Confirmed Working)
Use the `downloadUrl` from the artifact listing. It resolves to an `artprodwus21.artifacts.visualstudio.com` URL. **`web_fetch` cannot download binary ZIPs** — use PowerShell:
```powershell
# $downloadUrl = value from resource.downloadUrl in the artifacts list response
Invoke-WebRequest -Uri $downloadUrl -OutFile "$tmpDir\{triplet}.zip" -UseBasicParsing
Expand-Archive "$tmpDir\{triplet}.zip" -DestinationPath "$tmpDir\{triplet}" -Force
# List failing ports — each is a subdirectory inside the artifact folder
$root = "$tmpDir\{triplet}\failure logs for {triplet}"
Get-ChildItem $root -Directory | Select-Object -ExpandProperty Name
```
The ZIP always contains one top-level folder named exactly `"failure logs for {triplet}"`.
Inside that folder, each **failing port has its own subdirectory** named after the port.
> ❌ The Container API (`GET /_apis/resources/Containers/{containerId}`) does **not** work
> for `PipelineArtifact` type. Do not attempt to use it.
---
## Test Results Endpoints
### List Test Runs for a Build
```
GET https://dev.azure.com/{org}/{project}/_apis/test/runs?buildId={buildId}&api-version=7.0
```
**Response:**
```json
{
"value": [
{
"id": 9876,
"name": "x64-windows",
"state": "Completed",
"totalTests": 500,
"passedTests": 497,
"failedTests": 3
}
]
}
```
### Get Test Run Results
```
GET https://dev.azure.com/{org}/{project}/_apis/test/runs/{runId}/results?api-version=7.0&outcomes=Failed
```
Filter by `outcomes=Failed` to get only failures.
**Response includes:**
- `testCaseName` — usually `portname:triplet`
- `outcome` — `Failed`
- `errorMessage` — short error description
- `stackTrace` — full failure output
---
## URL Parsing
Given `https://dev.azure.com/vcpkg/public/_build/results?buildId=129315&view=results`:
| Component | Value | How to Extract |
|-----------|-------|----------------|
| Organization | `vcpkg` | 4th path segment |
| Project | `public` | 5th path segment |
| Build ID | `129315` | `buildId` query parameter |
```python
# Python example
from urllib.parse import urlparse, parse_qs
url = "https://dev.azure.com/vcpkg/public/_build/results?buildId=129315&view=results"
parts = urlparse(url)
segments = parts.path.strip('/').split('/')
org = segments[0] # "vcpkg"
project = segments[1] # "public"
build_id = parse_qs(parts.query)['buildId'][0] # "129315"
```
---
## Rate Limits and Auth
- **Public projects** (like `vcpkg/public`): No authentication needed; anonymous access is allowed
- **Rate limit**: ~200 requests per minute for anonymous; increase by adding a PAT as Bearer token if needed
- **PAT header**: `Authorization: Basic {base64(":pat_token")}`