Skip to content

Commit 4d71b56

Browse files
Merge branch 'main' into sdkauto/azure-ai-contentunderstanding-5580124
2 parents b089fbf + daf8953 commit 4d71b56

File tree

29 files changed

+684
-206
lines changed

29 files changed

+684
-206
lines changed

doc/analyze_check_versions.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ This table is to clarify the currently pinned version of tools we run in CI and
55

66
| Tool | Current Version | Next Version | Next Version Merge Date |
77
|------|-----------------|--------------|-------------------------|
8-
Pylint | 3.2.7 | 3.2.7 | 2026-01-12 |
9-
Pylint Guidelines Checker | 0.5.6 | 0.5.7 | 2026-01-12 |
10-
MyPy | 1.14.1 | 1.18.1 | 2026-01-12 |
11-
Pyright | 1.1.391 | 1.1.405 | 2026-01-12 |
8+
Pylint | 3.2.7 | 4.0.4 | 2026-04-13 |
9+
Pylint Guidelines Checker | 0.5.7 | 0.5.7 | 2026-04-13 |
10+
MyPy | 1.18.1 | 1.19.1 | 2026-04-13 |
11+
Pyright | 1.1.405 | 1.1.407 | 2026-04-13 |
1212
Sphinx | 8.2.0 | N/A | N/A |
1313
Black | 24.4.0 | N/A | N/A |
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Will output a variable named GH_TOKEN_<Owner> for each owner in TokenOwners if there is only one owner it will just output GH_TOKEN
2+
3+
parameters:
4+
- name: TokenOwners
5+
type: object
6+
default:
7+
- Azure
8+
- name: VariableNamePrefix
9+
type: string
10+
default: GH_TOKEN
11+
- name: ScriptDirectory
12+
default: eng/common/scripts
13+
14+
steps:
15+
- task: AzureCLI@2
16+
displayName: "Login to GitHub"
17+
inputs:
18+
azureSubscription: 'AzureSDKEngKeyVault Secrets'
19+
scriptType: pscore
20+
scriptLocation: scriptPath
21+
scriptPath: ${{ parameters.ScriptDirectory }}/login-to-github.ps1
22+
arguments: >
23+
-InstallationTokenOwners '${{ join(''',''', parameters.TokenOwners) }}'
24+
-VariableNamePrefix '${{ parameters.VariableNamePrefix }}'
25+

eng/common/pipelines/templates/steps/verify-links.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ parameters:
66
Recursive: $false
77
CheckLinkGuidance: $true
88
Urls: '(Get-ChildItem -Path ./ -Recurse -Include *.md)'
9-
BranchReplaceRegex: "^(${env:SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI}/(?:blob|tree)/)$(DefaultBranch)(/.*)$"
9+
BranchReplaceRegex: "^(${env:SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI}(?:\\.git)?/(?:blob|tree)/)$(DefaultBranch)(/.*)$"
1010
BranchReplacementName: "${env:SYSTEM_PULLREQUEST_SOURCECOMMITID}"
1111
Condition: succeeded() # If you want to run on failure for the link checker, set it to `Condition: succeededOrFailed()`.
1212

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
<#
2+
.SYNOPSIS
3+
Mints a GitHub App installation access token using Azure Key Vault 'sign' (non-exportable key),
4+
and logs in the GitHub CLI by setting GH_TOKEN.
5+
6+
.PARAMETER KeyVaultName
7+
Name of the Azure Key Vault containing the non-exportable RSA key.
8+
9+
.PARAMETER KeyName
10+
Name of the RSA key in Key Vault (imported as a *key*, not a secret).
11+
12+
.PARAMETER GitHubAppId
13+
Numeric App ID (not client ID) of your GitHub App.
14+
15+
.PARAMETER InstallationTokenOwners
16+
List of GitHub organizations or users for which to obtain installation tokens.
17+
18+
.PARAMETER VariableNamePrefix
19+
Name of the ADO variable to set when -SetPipelineVariable is used (default: GH_TOKEN).
20+
21+
.OUTPUTS
22+
Writes minimal info to stdout. Token is placed in $env:GH_TOKEN if there is only one owner otherwise $env:GH_TOKEN_<Owner> for each owner.
23+
#>
24+
25+
[CmdletBinding()]
26+
param(
27+
[string] $KeyVaultName = "azuresdkengkeyvault",
28+
[string] $KeyName = "azure-sdk-automation",
29+
[string] $GitHubAppId = '1086291', # Azure SDK Automation App ID
30+
[string[]] $InstallationTokenOwners = @("Azure"),
31+
[string] $VariableNamePrefix = "GH_TOKEN"
32+
)
33+
34+
$ErrorActionPreference = 'Stop'
35+
Set-StrictMode -Version Latest
36+
37+
$GitHubApiBaseUrl = "https://api.github.com"
38+
$GitHubApiVersion = "2022-11-28"
39+
40+
function Get-Headers {
41+
param(
42+
[Parameter(Mandatory)][string] $Jwt,
43+
[Parameter(Mandatory)][string] $ApiVersion
44+
)
45+
return @{
46+
'Authorization' = "Bearer $Jwt"
47+
'Accept' = 'application/vnd.github+json'
48+
'X-GitHub-Api-Version' = $ApiVersion
49+
'User-Agent' = 'ado-pwsh-ghapp'
50+
}
51+
}
52+
53+
function New-GitHubAppJwt {
54+
param(
55+
[Parameter(Mandatory)] [string] $VaultName,
56+
[Parameter(Mandatory)] [string] $KeyName,
57+
[Parameter(Mandatory)] [string] $AppId
58+
)
59+
60+
function Base64UrlEncode($json) {
61+
$bytes = [System.Text.Encoding]::UTF8.GetBytes($json)
62+
$base64 = [Convert]::ToBase64String($bytes)
63+
return $base64.TrimEnd('=') -replace '\+', '-' -replace '/', '_'
64+
}
65+
66+
# === STEP 1: Create JWT Header and Payload ===
67+
$Header = @{
68+
alg = "RS256"
69+
typ = "JWT"
70+
}
71+
$Now = [int][double]::Parse((Get-Date -UFormat %s))
72+
$Payload = @{
73+
iat = $Now
74+
exp = $Now + 600 # 10 minutes
75+
iss = $AppId
76+
}
77+
78+
$EncodedHeader = Base64UrlEncode (ConvertTo-Json $Header -Compress)
79+
$EncodedPayload = Base64UrlEncode (ConvertTo-Json $Payload -Compress)
80+
$UnsignedToken = "$EncodedHeader.$EncodedPayload"
81+
82+
# === STEP 2: Sign the token using Azure CLI ===
83+
$UnsignedTokenBytes = [System.Security.Cryptography.SHA256]::Create().ComputeHash([Text.Encoding]::ASCII.GetBytes($UnsignedToken))
84+
$Base64Value = [Convert]::ToBase64String($UnsignedTokenBytes)
85+
86+
$SignResultJson = az keyvault key sign `
87+
--vault-name $VaultName `
88+
--name $KeyName `
89+
--algorithm RS256 `
90+
--digest $Base64Value | ConvertFrom-Json
91+
92+
if ($LASTEXITCODE -ne 0) {
93+
throw "Failed to sign JWT with Azure Key Vault. Error: $SignResult"
94+
}
95+
96+
if (!$SignResultJson.signature) {
97+
throw "Azure Key Vault response does not contain a signature. Response: $($SignResultJson | ConvertTo-Json -Compress)"
98+
}
99+
100+
$Signature = $SignResultJson.signature
101+
return "$UnsignedToken.$Signature"
102+
}
103+
104+
function Get-GitHubInstallationId {
105+
param(
106+
[Parameter(Mandatory)][string] $Jwt,
107+
[Parameter(Mandatory)][string] $ApiBase,
108+
[Parameter(Mandatory)][string] $ApiVersion,
109+
[Parameter(Mandatory)][string] $InstallationTokenOwner
110+
)
111+
112+
$headers = Get-Headers -Jwt $Jwt -ApiVersion $ApiVersion
113+
114+
$uri = "$ApiBase/app/installations"
115+
$resp = Invoke-RestMethod -Method Get -Headers $headers -Uri $uri -TimeoutSec 30 -MaximumRetryCount 3
116+
117+
$resp | Foreach-Object { Write-Host " $($_.id): $($_.account.login) [$($_.target_type)]" }
118+
119+
$resp = $resp | Where-Object { $_.account.login -ieq $InstallationTokenOwner }
120+
if (!$resp.id) { throw "No installations found for this App." }
121+
return $resp.id
122+
}
123+
124+
function New-GitHubInstallationToken {
125+
param(
126+
[Parameter(Mandatory)] [string] $Jwt,
127+
[Parameter(Mandatory)] [string] $InstallationId,
128+
[Parameter(Mandatory)] [string] $ApiBase,
129+
[Parameter(Mandatory)] [string] $ApiVersion
130+
)
131+
$headers = Get-Headers -Jwt $Jwt -ApiVersion $ApiVersion
132+
$uri = "$ApiBase/app/installations/$InstallationId/access_tokens"
133+
$resp = Invoke-RestMethod -Method Post -Headers $headers -Uri $uri -TimeoutSec 30 -MaximumRetryCount 3
134+
if (!$resp.token) { throw "Failed to obtain installation access token for installation $InstallationId." }
135+
return $resp.token
136+
}
137+
138+
Write-Host "Generating GitHub App JWT by signing via Azure Key Vault (no key export)..."
139+
$jwt = New-GitHubAppJwt -VaultName $KeyVaultName -KeyName $KeyName -AppId $GitHubAppId
140+
141+
foreach ($InstallationTokenOwner in $InstallationTokenOwners)
142+
{
143+
Write-Host "Fetching installation ID for $InstallationTokenOwner ..."
144+
$installationId = Get-GitHubInstallationId -Jwt $jwt -ApiBase $GitHubApiBaseUrl -ApiVersion $GitHubApiVersion -InstallationTokenOwner $InstallationTokenOwner
145+
146+
Write-Host "Installation ID resolved: $installationId"
147+
148+
Write-Host "Exchanging JWT for installation access token..."
149+
$installationToken = New-GitHubInstallationToken -Jwt $jwt -InstallationId $installationId -ApiBase $GitHubApiBaseUrl -ApiVersion $GitHubApiVersion
150+
151+
$variableName = $VariableNamePrefix
152+
if ($InstallationTokenOwners.Count -gt 1)
153+
{
154+
$variableName = $VariableNamePrefix + "_" + $InstallationTokenOwner
155+
}
156+
157+
Set-Item -Path Env:$variableName -Value $installationToken
158+
159+
# Export for gh CLI & git
160+
Write-Host "$variableName has been set in the current process."
161+
162+
# Optionally set an Azure DevOps secret variable (so later tasks can reuse it)
163+
if ($null -ne $env:SYSTEM_TEAMPROJECTID) {
164+
Write-Host "##vso[task.setvariable variable=$variableName;issecret=true]$installationToken"
165+
Write-Host "Azure DevOps variable '$variableName' has been set (secret)."
166+
}
167+
168+
try {
169+
Write-Host "`n--- gh auth status ---"
170+
$gh_token_value_before = $env:GH_TOKEN
171+
$env:GH_TOKEN = $installationToken
172+
& gh auth status
173+
}
174+
finally{
175+
$env:GH_TOKEN = $gh_token_value_before
176+
}
177+
}

eng/common/testproxy/test-proxy-standalone-tool.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ steps:
7474
7575
# nohup does NOT continue beyond the current session if you use it within powershell
7676
- bash: |
77+
if [[ "$(uname)" == "Darwin" ]]; then
78+
export DOTNET_ROOT="$HOME/.dotnet"
79+
fi
7780
echo "nohup $(PROXY_EXE) 1>${{ parameters.rootFolder }}/test-proxy.log 2>${{ parameters.rootFolder }}/test-proxy-error.log &"
7881
nohup $(PROXY_EXE) 1>${{ parameters.rootFolder }}/test-proxy.log 2>${{ parameters.rootFolder }}/test-proxy-error.log &
7982
@@ -84,6 +87,8 @@ steps:
8487
displayName: "Run the testproxy - linux/mac"
8588
condition: and(succeeded(), ne(variables['Agent.OS'],'Windows_NT'), ${{ parameters.condition }})
8689
workingDirectory: "${{ parameters.rootFolder }}"
90+
env:
91+
DOTNET_ROLL_FORWARD: 'Major'
8792
8893
- pwsh: |
8994
for ($i = 0; $i -lt 10; $i++) {

eng/common/testproxy/test-proxy-tool.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ steps:
8787
8888
# nohup does NOT continue beyond the current session if you use it within powershell
8989
- bash: |
90+
if [[ "$(uname)" == "Darwin" ]]; then
91+
export DOTNET_ROOT="$HOME/.dotnet"
92+
fi
9093
nohup $(Build.BinariesDirectory)/test-proxy/test-proxy 1>${{ parameters.rootFolder }}/test-proxy.log 2>${{ parameters.rootFolder }}/test-proxy-error.log &
9194
9295
echo $! > $(Build.SourcesDirectory)/test-proxy.pid

0 commit comments

Comments
 (0)