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
21 changes: 15 additions & 6 deletions eng/common/pipelines/templates/steps/verify-codeowners.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,19 @@ parameters:
- data
- functions
- datamovement
# Comma separated list of emails allowed to skip codeowners validation. User
# must queue the pipeline manually with the 'Skip.VerifyCodeowners' variable
# set to 'true'.
# Comma separated list of Microsoft emails allowed to skip codeowners
# validation on manually queued builds. Build.RequestedForEmail is empty on
# pull request builds, so use AllowSkipAliases for those.
- name: AllowSkipEmails
type: string
default: ''
# Comma separated list of GitHub aliases (logins) allowed to skip codeowners
# validation on pull request builds. Matched against the GitHub login of the
# pull request author. Pull requests opened by a coding agent report the agent
# as the author, so they are not exempt.
- name: AllowSkipAliases
type: string
default: ''

steps:
- ${{ if and(eq(variables['Build.Reason'], 'PullRequest'), eq(parameters.EnablePrValidation, true)) }}:
Expand All @@ -41,9 +48,10 @@ steps:
pwsh: true
filePath: $(Build.SourcesDirectory)/eng/common/scripts/Set-VerifyCodeownersSkip.ps1
arguments: >-
-RequestedForEmail '$(Build.RequestedForEmail)'
-SkipVerifyCodeowners '$(Skip.VerifyCodeowners)'
-AdditionalSkipEmails '${{ parameters.AllowSkipEmails }}'
-BuildReason '$(Build.Reason)'
-Repo '${{ parameters.Repo }}'
-PullRequestNumber '$(System.PullRequest.PullRequestNumber)'
-AdditionalSkipAliases '${{ parameters.AllowSkipAliases }}'
workingDirectory: $(Build.SourcesDirectory)

- template: /eng/common/pipelines/templates/steps/install-azsdk-cli.yml
Expand Down Expand Up @@ -85,6 +93,7 @@ steps:
arguments: >-
-RequestedForEmail '$(Build.RequestedForEmail)'
-SkipVerifyCodeowners '$(Skip.VerifyCodeowners)'
-BuildReason '$(Build.Reason)'
-AdditionalSkipEmails '${{ parameters.AllowSkipEmails }}'
workingDirectory: $(Build.SourcesDirectory)

Expand Down
188 changes: 160 additions & 28 deletions eng/common/scripts/Set-VerifyCodeownersSkip.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,49 @@ Evaluates whether codeowners verification should be skipped and sets a pipeline
variable with the result.

.DESCRIPTION
Codeowners verification can be skipped when the 'Skip.VerifyCodeowners' variable
is set to 'true' and the person who requested the build is a member of an allowed
set of emails. The allowed set is the union of a default list and any additional
emails passed via the AdditionalSkipEmails parameter.

The result is written to an Azure DevOps pipeline variable (default name
'ShouldSkipVerifyCodeowners') so that later steps can gate on a single variable.
Verification is skipped only for an allow listed person. Who that is gets
established differently depending on how the build started:
- Pull request builds: the GitHub login of the pull request author, resolved
from the GitHub API. 'Build.RequestedForEmail' is empty here, because the
build is queued by the 'GitHub' app service principal rather than by a user.
- Manually queued builds: 'Build.RequestedForEmail', and only when the
'Skip.VerifyCodeowners' queue time variable is also 'true'. That variable
cannot be supplied on a pull request build, so it is ignored there.

Matching on the pull request author, rather than on whoever pushed most
recently, keeps the decision a property of the pull request. Agent opened pull
requests report the agent (for example 'Copilot') as the author, so they are
never exempt.

If the author cannot be resolved, verification runs. A GitHub outage or an
exhausted rate limit never blocks a build, and never silently grants a skip.

.PARAMETER RequestedForEmail
The email of the person who requested the build (e.g. Build.RequestedForEmail).
Only populated for manually queued builds.

.PARAMETER SkipVerifyCodeowners
The value of the 'Skip.VerifyCodeowners' pipeline variable. Verification is only
eligible to be skipped when this is 'true'.
The 'Skip.VerifyCodeowners' pipeline variable. Ignored on pull request builds,
where queue time variables cannot be supplied.

.PARAMETER BuildReason
The 'Build.Reason' pipeline variable.

.PARAMETER Repo
The GitHub repository in '<owner>/<name>' form (e.g. Build.Repository.Name).
Used to resolve the pull request author.

.PARAMETER PullRequestNumber
The pull request number (e.g. System.PullRequest.PullRequestNumber). Used to
resolve the pull request author.

.PARAMETER AdditionalSkipEmails
Additional emails allowed to skip verification, in addition to the
default set. Accepts a comma-, semicolon-, or whitespace-separated list.
Emails allowed to skip verification, in addition to the default set. Accepts a
comma-, semicolon-, or whitespace-separated list.

.PARAMETER AdditionalSkipAliases
GitHub aliases (logins) allowed to skip verification, in addition to the default
set. Accepts a comma-, semicolon-, or whitespace-separated list.

.PARAMETER OutputVariableName
The name of the pipeline variable to set with the boolean result.
Expand All @@ -30,19 +55,19 @@ The name of the pipeline variable to set with the boolean result.
param (
[string] $RequestedForEmail = '',
[string] $SkipVerifyCodeowners = $env:SKIP_VERIFYCODEOWNERS,
[string] $BuildReason = $env:BUILD_REASON,
[string] $Repo = $env:BUILD_REPOSITORY_NAME,
[string] $PullRequestNumber = $env:SYSTEM_PULLREQUEST_PULLREQUESTNUMBER,
[string] $AdditionalSkipEmails = '',
[string] $AdditionalSkipAliases = '',
[string] $OutputVariableName = 'ShouldSkipVerifyCodeowners'
)

Set-StrictMode -Version 4
$ErrorActionPreference = 'Stop'

if ($SkipVerifyCodeowners -ne 'true') {
Write-Host "Skip.VerifyCodeowners is not set to 'true' (value: '$SkipVerifyCodeowners'). Verification will run."
Write-Host "##vso[task.setvariable variable=$OutputVariableName]false"
return
}

# Microsoft emails allowed to skip verification. Only usable on manually queued
# builds, where Build.RequestedForEmail is populated.
$defaultSkipEmails = @(
'bebroder@microsoft.com',
'mharder@microsoft.com',
Expand All @@ -51,23 +76,130 @@ $defaultSkipEmails = @(
'raychen@microsoft.com'
)

$extraSkipEmails = @($AdditionalSkipEmails -split '[,;\s]+' | Where-Object { $_ })
# GitHub aliases (logins) allowed to skip verification. Used on pull request
# builds, where the GitHub login of the user who triggered the build is the only
# identity available. These are the same people as $defaultSkipEmails above; keep
# the two lists in sync.
$defaultSkipAliases = @(
'benbp',
'mikeharder',
'danieljurek',
'chidozieononiwu',
'raych1'
)

function Get-NormalizedSet {
param (
[string[]] $Default,
[string] $Additional
)

$extra = @($Additional -split '[,;\s]+' | Where-Object { $_ })

return @($Default + $extra) |
ForEach-Object { $_.Trim().ToLowerInvariant() } |
Where-Object { $_ } |
Select-Object -Unique
}

# Returns the GitHub login of the pull request author, or an empty string if it
# cannot be determined.
function Get-PullRequestAuthor {
param (
[string] $Repo,
[string] $PullRequestNumber
)

$uri = "https://api.github.com/repos/$Repo/pulls/$PullRequestNumber"

# The request is unauthenticated and subject to GitHub's anonymous per IP
# rate limit. Retries are kept low, because every attempt spends budget.
try {
$pullRequest = Invoke-RestMethod -Uri $uri -MaximumRetryCount 3 -RetryIntervalSec 2
$author = "$($pullRequest.user.login)"

if ($pullRequest.user.type -eq 'Bot') {
Write-Host "Pull request $PullRequestNumber was opened by the '$author' app rather than by a person, so it is attributed to the app and not to whoever dispatched it. Verification will run."
}
Comment thread
danieljurek marked this conversation as resolved.

return $author
} catch {
Write-Host "Failed to resolve the author of '$uri': $_"
return ''
}
}

function Set-Result {
param (
[bool] $ShouldSkip
)

$value = $ShouldSkip.ToString().ToLowerInvariant()
Write-Host "Setting $OutputVariableName to $value"
Write-Host "##vso[task.setvariable variable=$OutputVariableName]$value"
}

$isPullRequest = $BuildReason -eq 'PullRequest'

Write-Host "Build.Reason: $BuildReason"

# Skip.VerifyCodeowners is a queue-time variable, which cannot be supplied on a
# pull request build, so it only applies outside of pull requests. There,
# skipping must be explicitly requested.
if (!$isPullRequest) {
Write-Host "Skip.VerifyCodeowners: $SkipVerifyCodeowners"

if ($SkipVerifyCodeowners -ne 'true') {
Write-Host "This is not a pull request build and Skip.VerifyCodeowners is not set to 'true' (value: '$SkipVerifyCodeowners'). Verification will run."
Set-Result -ShouldSkip $false
return
}
}

$allowedSkipAliases = Get-NormalizedSet -Default $defaultSkipAliases -Additional $AdditionalSkipAliases

if ($isPullRequest) {
# Build.RequestedForEmail is empty on GitHub pull request builds, so match on
# the PR author's GitHub login instead.
$pullRequestAuthor = Get-PullRequestAuthor -Repo $Repo -PullRequestNumber $PullRequestNumber

$normalizedAlias = $pullRequestAuthor.Trim().ToLowerInvariant()

$allowedSkipEmails = @($defaultSkipEmails + $extraSkipEmails) |
ForEach-Object { $_.Trim().ToLowerInvariant() } |
Where-Object { $_ } |
Select-Object -Unique
if (!$normalizedAlias) {
Write-Host "Could not determine the pull request author. Codeowners verification will run."
Set-Result -ShouldSkip $false
return
}

Write-Host "Pull request author: $normalizedAlias"

if ($allowedSkipAliases -notcontains $normalizedAlias) {
Write-Host "The GitHub alias '$normalizedAlias' is not in the allowed skip list."
Set-Result -ShouldSkip $false
return
}

Write-Host "The GitHub alias '$normalizedAlias' is allowed to skip verification."
Set-Result -ShouldSkip $true
return
}

$allowedSkipEmails = Get-NormalizedSet -Default $defaultSkipEmails -Additional $AdditionalSkipEmails
$normalizedEmail = $RequestedForEmail.Trim().ToLowerInvariant()

$shouldSkip = $allowedSkipEmails -contains $normalizedEmail
if (!$normalizedEmail) {
Write-Host "Could not determine the email of the person who requested the build. Verification will run."
Set-Result -ShouldSkip $false
return
}

if ($shouldSkip) {
Write-Host "Skipping codeowners verification. Skip.VerifyCodeowners is set and '$normalizedEmail' is an allowed email."
} else {
Write-Host "Skip.VerifyCodeowners is set but '$normalizedEmail' is not an allowed email. Verification will run."
if ($allowedSkipEmails -notcontains $normalizedEmail) {
Write-Host "The email '$normalizedEmail' is not in the allowed skip list."
Set-Result -ShouldSkip $false
return
}

Write-Host "##vso[task.setvariable variable=$OutputVariableName]$($shouldSkip.ToString().ToLowerInvariant())"
Write-Host "The email '$normalizedEmail' is allowed to skip verification."
Set-Result -ShouldSkip $true

return
Loading