Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
216dd65
OSOE-1284 Fix deprecated workflow action runtimes
Piedone Sep 5, 2026
313d753
OSOE-1284 Reset mocked exit code after label tests
Piedone Sep 5, 2026
b8f6fc1
OSOE-1284 Split telemetry migration notes for text lint
Piedone Sep 5, 2026
06d9f71
OSOE-1284 Apply PowerShell analysis rules and case-insensitive label …
Piedone Sep 5, 2026
9d021f1
OSOE-1284 Default test workflows to Microsoft Testing Platform
Piedone Sep 5, 2026
699e0d6
Revert "OSOE-1284 Default test workflows to Microsoft Testing Platform"
Piedone Sep 5, 2026
697aac6
Renaming parameters
Piedone Sep 6, 2026
9fbf901
Formatting
Piedone Sep 6, 2026
af90b30
Revert "Formatting"
Piedone Sep 6, 2026
86c53bd
Rename
Piedone Sep 6, 2026
10a955d
OSOE-1284 Document single-line JSON secrets and test nested telemetry
Piedone Sep 6, 2026
d843f5f
Remove unnecessary tests
Piedone Sep 6, 2026
9845886
Running a fork of Twitter, together! instead
Piedone Sep 6, 2026
a8e31da
OSOE-1284 Use caller workflow token for telemetry
Piedone Sep 7, 2026
422bc54
Unnecessary note
Piedone Sep 7, 2026
9dc7416
OSOE-1284 Pass label script inputs as explicit parameters
Piedone Sep 7, 2026
96f7552
OSOE-1284 Use dedicated gh commands to edit labels
Piedone Sep 7, 2026
725b4a2
OSOE-1284 Avoid organization scope requirement for PR labels
Piedone Sep 7, 2026
2dccd8e
Better texts
Piedone Sep 7, 2026
2bcfc7e
Standardizing order of parameters
Piedone Sep 7, 2026
a873ecb
Conventional script loading
Piedone Sep 7, 2026
b8c57fd
Linter fix
Piedone Sep 7, 2026
28a38c6
Safer CSV escaping
Piedone Sep 7, 2026
753f3fe
Removing singular Label from add-remove-label
Piedone Sep 7, 2026
fa75d13
Maybe
Piedone Sep 7, 2026
8a469cd
Revert branch selectors.
sarahelsaig Sep 8, 2026
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
115 changes: 115 additions & 0 deletions .github/actions/add-remove-label/Test-UpdateLabels.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
$errorActionPreference = 'Stop'
$eventPath = Join-Path ([IO.Path]::GetTempPath()) ([IO.Path]::GetRandomFileName())
$testState = @{
Calls = [Collections.Generic.List[object]]::new()
ExistingLabels = @()
FailureMethod = ''
}

# Mock the CLI so these tests never change a real issue or pull request.
function gh
{
# Model a token that has "repo" scope, but no "read:org" scope. Calling "gh pr edit" queries team reviewers, even
# when only editing labels (see https://github.com/cli/cli/issues/13575).
if ($args[0] -eq 'pr' -and $args[1] -eq 'edit')
{
throw 'The "pr edit" query requires "read:org" OAuth scope.'
}

$testState.Calls.Add(@{ Arguments = @($args) })
$global:LASTEXITCODE = 0
if ($testState.FailureMethod -and $args -contains $testState.FailureMethod)
{
$global:LASTEXITCODE = 1
}
if ($args -contains 'view') { $testState.ExistingLabels }
}

function Assert-True($Condition, $Message)
{
if (-not $Condition) { throw $Message }
Comment thread
sarahelsaig marked this conversation as resolved.
}

function Invoke-TestUpdate($EventJson, $Operation, $Labels = '')
{
$testState.Calls.Clear()
Set-Content -LiteralPath $eventPath -Value $EventJson
$parameters = @{
EventPath = $eventPath
Repository = 'owner/repo'
Operation = $Operation
Labels = $Labels
}
& "$PSScriptRoot/Update-Labels.ps1" @parameters
}

try
{
Invoke-TestUpdate -EventJson '{"pull_request":{"number":42}}' -Operation add -Labels 'a, single label'
Assert-True ($testState.Calls.Count -eq 1) 'Adding a label must make one edit call.'
Comment thread
sarahelsaig marked this conversation as resolved.
$arguments = $testState.Calls[0].Arguments
Assert-True (($arguments[0..4] -join '|') -ceq 'issue|edit|42|--repo|owner/repo') 'PR labels must use issue edit.'
Assert-True ($arguments[5] -ceq '--add-label' -and $arguments[6] -ceq '"a","single label"') 'Comma-separated labels must be CSV quoted.'

Invoke-TestUpdate -EventJson '{"issue":{"number":7}}' -Operation add -Labels ' first, ,second '
$arguments = $testState.Calls[0].Arguments
Assert-True (($arguments[0..4] -join '|') -ceq 'issue|edit|7|--repo|owner/repo') 'Wrong issue edit command.'
Assert-True ($arguments[6] -ceq '"first","second"') 'Labels must be trimmed and unique values maintained.'

$specialLabel = 'quote" slash/ # & $(never-execute)'
Invoke-TestUpdate -EventJson '{"issue":{"number":7}}' -Operation add -Labels $specialLabel
$expectedLabel = '"quote"" slash/ # & $(never-execute)"'
Assert-True ($testState.Calls[0].Arguments[6] -ceq $expectedLabel) 'Quotes must be escaped as CSV data.'

$testState.ExistingLabels = @($specialLabel)
Invoke-TestUpdate -EventJson '{"pull_request":{"number":42}}' -Operation remove -Labels $specialLabel
Assert-True (($testState.Calls[0].Arguments[0..2] -join '|') -ceq 'pr|view|42') 'PR labels must be read with pr view.'
$arguments = $testState.Calls[1].Arguments
Assert-True ($arguments[0] -ceq 'issue' -and $arguments[5] -ceq '--remove-label') 'PR label removal must use issue edit.'
Assert-True ($arguments[6] -ceq $expectedLabel) 'Removal must preserve special characters.'

$testState.ExistingLabels = @('present')
Invoke-TestUpdate -EventJson '{"issue":{"number":7}}' -Operation remove -Labels 'present, missing, present'
Assert-True ($testState.Calls.Count -eq 2) 'Removal should make one view and one edit call.'
Assert-True ($testState.Calls[1].Arguments[6] -ceq '"present"') 'Only existing, unique labels should be removed.'

Invoke-TestUpdate -EventJson '{"issue":{"number":7}}' -Operation remove -Labels PRESENT
Assert-True ($testState.Calls[1].Arguments[6] -ceq '"present"') 'Lookup must preserve the existing label casing.'

$testState.ExistingLabels = @()
Invoke-TestUpdate -EventJson '{"issue":{"number":7}}' -Operation remove -Labels present
Assert-True ($testState.Calls.Count -eq 1) 'Removing an absent label must succeed without an edit.'

Invoke-TestUpdate -EventJson '{"ref":"refs/heads/dev"}' -Operation add -Labels example
Assert-True ($testState.Calls.Count -eq 0) 'Push events must not make label requests.'
Invoke-TestUpdate -EventJson '{"issue":{"number":7}}' -Operation add
Assert-True ($testState.Calls.Count -eq 0) 'Empty labels must not make requests.'

foreach ($method in @('--add-label', 'view', '--remove-label'))
{
$testState.FailureMethod = $method
$testState.ExistingLabels = @('present')
$failed = $false
try
{
$operation = $method -eq '--add-label' ? 'add' : 'remove'
Invoke-TestUpdate -EventJson '{"issue":{"number":7}}' -Operation $operation -Labels present
}
catch { $failed = $true }
Assert-True $failed "A failed $method command must fail the action."
}

$failed = $false
$testState.FailureMethod = ''
try { Invoke-TestUpdate -EventJson '{"issue":{"number":7}}' -Operation invalid -Labels example }
catch { $failed = $true }
Assert-True $failed 'Invalid operations must fail.'

# The Actions PowerShell shell propagates LASTEXITCODE, including our intentionally mocked CLI failures.
$global:LASTEXITCODE = 0
Write-Output 'All label update tests passed.'
}
finally
{
Remove-Item -LiteralPath $eventPath
}
63 changes: 63 additions & 0 deletions .github/actions/add-remove-label/Update-Labels.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
param(
[Parameter(Mandatory)]
[string] $EventPath,
[Parameter(Mandatory)]
[string] $Repository,
[string] $Labels = '',
[Parameter(Mandatory)]
[string] $Operation
)

$errorActionPreference = 'Stop'

$githubEvent = Get-Content -LiteralPath $EventPath -Raw | ConvertFrom-Json
$number = $githubEvent.pull_request.number ?? $githubEvent.issue.number

# Push and other events without an issue or pull request have no labels to update.
if (-not $number)
{
return
}

if ($Operation -cnotin @('add', 'remove'))
{
throw 'The label operation must be add or remove.'
}

$labelsToUpdate = @(
if ($Labels)
{
$Labels.Split(',').Trim() | Where-Object { $PSItem }
}
)

if ($labelsToUpdate.Count -eq 0)
{
return
}

$command = $githubEvent.pull_request ? 'pr' : 'issue'

if ($Operation -ceq 'remove')
{
# Removing an absent label should succeed, including on repeated workflow runs.
$existingLabels = @(gh $command view $number --repo $Repository --json labels --jq '.labels[].name')
if ($LASTEXITCODE -ne 0) { throw 'Failed to read labels.' }

$labelsToUpdate = @($existingLabels | Where-Object { $labelsToUpdate -contains $PSItem })
if ($labelsToUpdate.Count -eq 0) { return }
}

# gh parses label flags as CSV so labels should be escaped.
$labelNames = (
$labelsToUpdate |
Select-Object -Unique |
ForEach-Object { @{ Value = $PSItem } } |
ConvertTo-Csv -UseQuotes Always -NoHeader
) -join ','

$labelFlag = "--$Operation-label"
# issue edit also supports PRs and only queries the edited fields. pr edit unconditionally fetches team reviewers,
# requiring read:org even for label-only changes made with an otherwise sufficient repo-scoped token.
gh issue edit $number --repo $Repository $labelFlag $labelNames
if ($LASTEXITCODE -ne 0) { throw "Failed to $Operation labels." }
34 changes: 21 additions & 13 deletions .github/actions/add-remove-label/action.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
name: Add/Remove Label
description: >
Runs buildsville/add-remove-label. Exists only to centralize which version of the action we use. Intentionally not
documented in Actions.md since it's only meant for internal use.
Updates issue and pull request labels with the GitHub CLI. Intentionally not documented in Actions.md, because it's
only meant for internal use.

# Copied from https://github.com/buildsville/add-remove-label/blob/master/action.yml.
inputs:
token:
description: github token
Expand All @@ -13,10 +12,6 @@ inputs:
description: labels to edit
required: false
default: ''
label:
description: label to edit
required: false
default: ''
type:
description: add or remove
required: true
Expand All @@ -25,10 +20,23 @@ inputs:
runs:
using: composite
steps:
- name: Setup Scripts
shell: pwsh
run: |
'${{ github.action_path }}' >> $Env:GITHUB_PATH

- name: Add/Remove Label
uses: buildsville/add-remove-label@ac59c9f0aeb66eb12d6366eb1d69ec1906e9ef9a # v2.0.1
with:
token: ${{ inputs.token }}
labels: ${{ inputs.labels }}
label: ${{ inputs.label }}
type: ${{ inputs.type }}
shell: pwsh
env:
GH_TOKEN: ${{ inputs.token }}
LABELS: ${{ inputs.labels }}
LABEL_OPERATION: ${{ inputs.type }}
run: |
$parameters = @{
EventPath = $Env:GITHUB_EVENT_PATH
Repository = $Env:GITHUB_REPOSITORY
Labels = $Env:LABELS
Operation = $Env:LABEL_OPERATION
}
Comment thread
sarahelsaig marked this conversation as resolved.

Update-Labels @parameters
2 changes: 1 addition & 1 deletion .github/actions/mark-breaking-changes/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ runs:
uses: Lombiq/GitHub-Actions/.github/actions/add-remove-label@dev
with:
token: ${{ env.GITHUB_TOKEN }}
label: breaking-changes
labels: breaking-changes
type: ${{ inputs.is-breaking == 'true' && 'add' || 'remove' }}

- name: Update Pull Request Title
Expand Down
73 changes: 9 additions & 64 deletions .github/actions/workflow-telemetry/action.yml
Original file line number Diff line number Diff line change
@@ -1,78 +1,23 @@
name: Workflow Telemetry
description: >
Runs catchpoint/workflow-telemetry-action. Exists only to centralize which version of the action we use. Intentionally
not documented in Actions.md since it's only meant for internal use.

# Copied from https://github.com/catchpoint/workflow-telemetry-action/blob/master/action.yml with code styling and
# comment_on_pr defaulting to false.
Collects CPU and memory metrics and displays Mermaid charts in the job summary without an external chart service.
Intentionally not documented in Actions.md since it's only meant for internal use.

inputs:
github_token:
description: GitHub API Access Token
description: GitHub API access token. Requires actions:read for private repositories.
default: ${{ github.token }}
required: false
metric_frequency:
description: Metric collection frequency in seconds. Must be a number. Defaults to '5'.
default: 5
required: false
proc_trace_min_duration:
description: >
Puts minimum limit for process execution duration to be traced. Must be a number. Defaults to '-1' which means
process duration filtering is not applied.
default: -1
required: false
proc_trace_sys_enable:
description: Enables tracing default system processes ('aws', 'cat', 'sed', ...). Defaults to 'false'.
default: false
required: false
proc_trace_chart_show:
description: Enables showing traced processes in trace chart. Defaults to 'true'.
default: true
required: false
proc_trace_chart_max_count:
description: >
Maximum number of processes to be shown in trace chart (applicable if `proc_trace_chart_show` input is `true`).
Must be a number. Defaults to '100'.
default: 100
required: false
proc_trace_table_show:
description: Enables showing traced processes in trace table. Defaults to 'false'.
default: false
required: false
comment_on_pr:
description: >
Set to `true` to publish the results as comment to the PR (applicable if workflow run is triggered from PR).
Defaults to 'false'.
default: false
required: false
job_summary:
description: >
Set to `true` to publish the results as part of the job summary page of the workflow run. Defaults to 'true'.
default: true
required: false
theme:
description: Set to `dark` to generate charts compatible with Github dark mode. Defaults to 'light'.
default: light
interval_seconds:
description: Interval between metrics collection in seconds.
default: '5'
required: false

runs:
using: composite
steps:
- name: Set Checkout Token
uses: Lombiq/GitHub-Actions/.github/actions/set-checkout-token@dev
with:
checkout-token: ${{ inputs.github_token }}

- name: Collect Workflow Telemetry
uses: catchpoint/workflow-telemetry-action@94c3c3d9567a0205de6da68a76c428ce4e769af1 # v2.0.0
uses: dev-hato/actions-workflow-metrics@c6b748e52274db07791b2117b97315c38660e333 # v0.0.8
with:
github_token: ${{ env.CHECKOUT_TOKEN }}
metric_frequency: ${{ inputs.metric_frequency }}
proc_trace_min_duration: ${{ inputs.proc_trace_min_duration }}
proc_trace_sys_enable: ${{ inputs.proc_trace_sys_enable }}
proc_trace_chart_show: ${{ inputs.proc_trace_chart_show }}
proc_trace_chart_max_count: ${{ inputs.proc_trace_chart_max_count }}
proc_trace_table_show: ${{ inputs.proc_trace_table_show }}
comment_on_pr: ${{ inputs.comment_on_pr }}
job_summary: ${{ inputs.job_summary }}
theme: ${{ inputs.theme }}
github_token: ${{ inputs.github_token || github.token }}
interval_seconds: ${{ inputs.interval_seconds }}
18 changes: 11 additions & 7 deletions .github/workflows/build-and-test-dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ on:
required: false
description: >
A JSON string containing key-value pairs of environment variables to be set. You can use this to pass in
arbitrary environment variables that can be used to e.g. customize the build or test execution. Example:
ENVIRONMENT_VARIABLES_JSON: |
arbitrary environment variables that can be used to e.g. customize the build or test execution.
Pass a single-line JSON string: GitHub masks each line of multiline secrets, including standalone braces,
which breaks telemetry charts. When using >-, keep all JSON lines equally indented so YAML folds every
line break. Example:
ENVIRONMENT_VARIABLES_JSON: >-
{
"MY_ENV_VAR": "value of environment variable",
"MY_ENV_VAR2": "value of environment variable"
"MY_ENV_VAR": "value of environment variable",
"MY_ENV_VAR2": "value of environment variable"
}

inputs:
Expand Down Expand Up @@ -232,8 +235,8 @@ on:
type: string
default: 'true'
description: >
If "true" (the default), detailed telemetry about the workflow run will be collected with
https://github.com/catchpoint/workflow-telemetry-action.
If "true" (the default), CPU and memory usage charts and a JSON artifact will be generated with
https://github.com/dev-hato/actions-workflow-metrics. Private repositories require actions:read permission.

jobs:
# While the below steps seem suitable to DRY with build-and-test-orchard-core, since reusable workflows can't call
Expand Down Expand Up @@ -270,7 +273,8 @@ jobs:
if: inputs.collect-workflow-telemetry == 'true'
uses: Lombiq/GitHub-Actions/.github/actions/workflow-telemetry@dev
with:
github_token: ${{ secrets.CHECKOUT_TOKEN }}
# Telemetry reads the caller's workflow run, which may be in a different repository from checkout.
github_token: ${{ github.token }}

- name: Set Environment Variables
uses: Lombiq/GitHub-Actions/.github/actions/set-environment-variables@dev
Expand Down
Loading