Skip to content

Add taskTemplates to TaskSpawner for per-item pipeline spawning #4730

Add taskTemplates to TaskSpawner for per-item pipeline spawning

Add taskTemplates to TaskSpawner for per-item pipeline spawning #4730

Workflow file for this run

name: Label
on:
issues:
types: [opened, edited, labeled, unlabeled]
pull_request_target:
types: [opened, edited, labeled, unlabeled, synchronize]
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
pull_request_review:
types: [submitted]
concurrency:
group: ${{ github.workflow }}-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
permissions:
issues: write
pull-requests: write
jobs:
comment-label:
if: >-
(
github.event_name == 'issue_comment' ||
github.event_name == 'pull_request_review_comment' ||
github.event_name == 'pull_request_review' ||
(github.event_name == 'issues' && (github.event.action == 'opened' || github.event.action == 'edited')) ||
(github.event_name == 'pull_request_target' && (github.event.action == 'opened' || github.event.action == 'edited'))
) && (
contains(github.event.comment.body || github.event.review.body || github.event.issue.body || github.event.pull_request.body || '', '/kind') ||
contains(github.event.comment.body || github.event.review.body || github.event.issue.body || github.event.pull_request.body || '', '/priority') ||
contains(github.event.comment.body || github.event.review.body || github.event.issue.body || github.event.pull_request.body || '', '/actor') ||
contains(github.event.comment.body || github.event.review.body || github.event.issue.body || github.event.pull_request.body || '', '/triage')
)
runs-on: ubuntu-latest
steps:
- name: Resolve command context
id: context
uses: actions/github-script@v7
with:
script: |
const body =
context.payload.comment?.body ||
context.payload.review?.body ||
context.payload.issue?.body ||
context.payload.pull_request?.body ||
''
// Resolve the actor from the author of the text containing the
// label command, not from the event sender. When a bot edits
// a PR description the sender is the bot, but the PR author
// (who wrote /kind, /priority, etc.) is the relevant actor for
// permission checks.
const actor =
context.payload.comment?.user?.login ||
context.payload.review?.user?.login ||
context.payload.issue?.user?.login ||
context.payload.pull_request?.user?.login ||
''
const issueNumber = context.payload.issue?.number || context.payload.pull_request?.number || 0
core.setOutput('body', body)
core.setOutput('actor', actor)
core.setOutput('issue_number', String(issueNumber))
if (!actor) {
core.setFailed('Could not determine actor for label command')
}
if (!issueNumber) {
core.setFailed('Could not determine issue number for label command')
}
- name: Check permission
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PERMISSION=$(gh api "repos/${{ github.repository }}/collaborators/${{ steps.context.outputs.actor }}/permission" -q .permission)
if [[ "$PERMISSION" != "admin" && "$PERMISSION" != "write" ]]; then
echo "User ${{ steps.context.outputs.actor }} does not have write permission (permission=$PERMISSION)"
exit 1
fi
- name: Apply labels
uses: actions/github-script@v7
env:
COMMAND_BODY: ${{ steps.context.outputs.body }}
ISSUE_NUMBER: ${{ steps.context.outputs.issue_number }}
with:
script: |
const rawBody = process.env.COMMAND_BODY || '';
// Strip HTML comments so that example commands in PR
// templates (<!-- /kind bug -->) are not matched.
const body = rawBody.replace(/<!--[\s\S]*?-->/g, '');
const issueNumber = Number(process.env.ISSUE_NUMBER || '0');
const owner = context.repo.owner;
const repo = context.repo.repo;
if (!issueNumber) {
throw new Error('Issue number is required to apply labels');
}
const commands = [
{ command: 'kind', prefix: 'kind/' },
{ command: 'priority', prefix: 'priority/' },
{ command: 'actor', prefix: 'actor/' },
];
const toAdd = [];
const toRemove = [];
for (const { command, prefix } of commands) {
const addPattern = new RegExp(`^\\s*/(?:remove-)?${command}\\s+(.+)$`, 'gm');
let match;
while ((match = addPattern.exec(body)) !== null) {
const line = match[0].trim();
const value = match[1].trim();
const label = `${prefix}${value}`;
if (line.startsWith(`/remove-${command}`)) {
toRemove.push(label);
} else {
toAdd.push(label);
}
}
}
// Handle /triage-accepted and /remove-triage-accepted
if (/^\s*\/triage-accepted\s*$/m.test(body)) {
toAdd.push('triage-accepted');
}
if (/^\s*\/remove-triage-accepted\s*$/m.test(body)) {
toRemove.push('triage-accepted');
}
if (toAdd.length === 0 && toRemove.length === 0) {
core.info('No label commands found');
return;
}
if (toAdd.length > 0) {
core.info(`Adding labels: ${toAdd.join(', ')}`);
await github.rest.issues.addLabels({
owner,
repo,
issue_number: issueNumber,
labels: toAdd,
});
}
for (const label of toRemove) {
core.info(`Removing label: ${label}`);
try {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: issueNumber,
name: label,
});
} catch (e) {
if (e.status !== 404) throw e;
core.warning(`Label ${label} was not present`);
}
}
sync-labels:
if: always() && !cancelled() && (needs.comment-label.result == 'success' || needs.comment-label.result == 'skipped')
needs: comment-label
runs-on: ubuntu-latest
steps:
- name: Sync needs-* labels
uses: actions/github-script@v7
with:
script: |
const item = context.payload.issue || context.payload.pull_request;
const { data: fresh } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: item.number,
});
const labels = fresh.map(l => l.name);
const rules = [
{ prefix: null, match: 'triage-accepted', needs: 'needs-triage' },
{ prefix: 'kind/', match: null, needs: 'needs-kind' },
{ prefix: 'priority/', match: null, needs: 'needs-priority' },
{ prefix: 'actor/', match: null, needs: 'needs-actor' },
];
const errors = [];
for (const rule of rules) {
try {
const satisfied = rule.match
? labels.includes(rule.match)
: labels.some(l => l.startsWith(rule.prefix));
const hasNeeds = labels.includes(rule.needs);
if (!satisfied && !hasNeeds) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: item.number,
labels: [rule.needs],
});
} else if (satisfied && hasNeeds) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: item.number,
name: rule.needs,
});
} catch (e) {
if (e.status !== 404) throw e;
}
}
} catch (e) {
errors.push({ rule: rule.needs, error: e });
core.warning(`Failed to sync ${rule.needs}: ${e.message}`);
}
}
// For PRs, check whether the body contains a release-note block and
// sync release-note / release-note-none / needs-release-note labels.
const pr = context.payload.pull_request;
if (pr) {
try {
const body = pr.body || '';
const match = body.match(/```release-note\s*\n([\s\S]*?)```/);
const noteContent = match ? match[1].trim() : '';
const isNone = noteContent.toLowerCase() === 'none';
const hasNote = noteContent.length > 0 && !isNone;
const desiredLabel = hasNote ? 'release-note' : isNone ? 'release-note-none' : null;
const releaseLabels = ['release-note', 'release-note-none', 'needs-release-note'];
for (const label of releaseLabels) {
const present = labels.includes(label);
const wanted = label === desiredLabel || (label === 'needs-release-note' && desiredLabel === null);
if (wanted && !present) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: [label],
});
} else if (!wanted && present) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
name: label,
});
} catch (e) {
if (e.status !== 404) throw e;
}
}
}
} catch (e) {
errors.push({ rule: 'release-note', error: e });
core.warning(`Failed to sync release-note labels: ${e.message}`);
}
}
if (errors.length > 0) {
throw new Error(`Failed to sync labels: ${errors.map(e => e.rule).join(', ')}`);
}
check-pr-labels:
runs-on: ubuntu-latest
needs: sync-labels
if: always() && github.event.pull_request
permissions:
issues: read
steps:
- name: Check for blocking labels
uses: actions/github-script@v7
with:
script: |
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
});
const blocking = ['needs-kind', 'needs-release-note'];
const found = blocking.filter(b => labels.some(l => l.name === b));
if (found.length > 0) {
core.setFailed(`PR is missing required labels. Blocking labels found: ${found.join(', ')}`);
} else {
core.info('All required labels are present');
}