-
Notifications
You must be signed in to change notification settings - Fork 7
162 lines (149 loc) · 6.59 KB
/
Copy pathtest-report-check.yml
File metadata and controls
162 lines (149 loc) · 6.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# © 2026 NetApp, Inc. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
# See the NOTICE file in the repo root for trademark and attribution details.
name: Test Report Check
# Soft gate: nudges contributors to fill in the PR body's Test Report section
# whenever a PR touches example code under python/, ansible/, terraform/, or go/.
# Applies a 'needs-test-report' label and a sticky comment when the section
# is empty; clears them once it's filled. Does NOT fail the build.
#
# Uses pull_request_target so it works on PRs from forks. Safe because the
# workflow does no checkout and only calls REST APIs against PR metadata.
on:
pull_request_target:
types: [opened, edited, synchronize, reopened, ready_for_review, labeled, unlabeled]
branches: [main]
permissions:
pull-requests: write
issues: write
contents: read
jobs:
check:
name: Check for Test Report
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Detect example-code changes
id: scope
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
per_page: 100,
});
// Must stay in step with TESTING.md and the PR template, which both
// require a Test Report for Go changes too.
const EXAMPLE_DIRS = ['python/', 'ansible/', 'terraform/', 'go/'];
const touched = files.some(f =>
EXAMPLE_DIRS.some(d => f.filename.startsWith(d))
);
core.setOutput('touched', String(touched));
- name: Evaluate Test Report and label
env:
TOUCHED: ${{ steps.scope.outputs.touched }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const LABEL = 'needs-test-report';
const STICKY_KEY = '<!-- pace:test-report-check -->';
const owner = context.repo.owner;
const repo = context.repo.repo;
const issue_number = context.issue.number;
const TESTING_URL =
`https://github.com/${owner}/${repo}/blob/main/TESTING.md`;
const touched = process.env.TOUCHED === 'true';
async function findSticky() {
const { data } = await github.rest.issues.listComments({
owner, repo, issue_number, per_page: 100,
});
return data.find(c => c.body && c.body.includes(STICKY_KEY));
}
async function upsertSticky(msg) {
const existing = await findSticky();
const body = `${STICKY_KEY}\n${msg}`;
if (existing) {
await github.rest.issues.updateComment({
owner, repo, comment_id: existing.id, body,
});
} else {
await github.rest.issues.createComment({
owner, repo, issue_number, body,
});
}
}
async function removeLabel() {
try {
await github.rest.issues.removeLabel({
owner, repo, issue_number, name: LABEL,
});
} catch (e) {
if (e.status !== 404) throw e;
}
}
async function addLabel() {
await github.rest.issues.addLabels({
owner, repo, issue_number, labels: [LABEL],
});
}
if (!touched) {
await removeLabel();
core.info(
'PR does not touch python/ansible/terraform/go - skipping.'
);
return;
}
const { data: pr } = await github.rest.pulls.get({
owner, repo, pull_number: issue_number,
});
const body = pr.body || '';
// Tiered soft gate: skip Test Report nag for PRs that
// are docs-only, ci-only, or have been judged trivial by
// a maintainer. The 'docs-only' / 'ci-only' labels are
// applied automatically by .github/workflows/pr-labeler.yml
// (config in .github/labeler.yml). 'trivial' is applied by
// hand when warranted.
const labels = (pr.labels || []).map(l => l.name);
const SKIP_LABELS = ['docs-only', 'ci-only', 'trivial'];
const skipReason = SKIP_LABELS.find(l => labels.includes(l));
if (skipReason) {
await removeLabel();
await upsertSticky(
`Test Report skipped — this PR is labelled \`${skipReason}\`. ` +
'Re-evaluation will run automatically if that label is removed ' +
'or the PR scope expands to touch `python/`, `ansible/`, ' +
'`terraform/`, or `go/`.'
);
core.info(`Skipped due to label: ${skipReason}`);
return;
}
const hasSection = body.includes('## Test Report');
const stillRequiredMarker = body.includes('TEST_REPORT_REQUIRED');
const envMatch = body.match(/\*\*Environment:\*\*\s*([^\n<]*)/);
const envFilled = !!(envMatch && envMatch[1].trim().length > 0);
const filled = hasSection && envFilled && !stillRequiredMarker;
if (filled) {
await removeLabel();
await upsertSticky(
'Test Report looks present - thanks. Reviewers will verify ' +
'the captured output and idempotency evidence.'
);
} else {
await addLabel();
await upsertSticky(
'**Test Report missing or unfilled.**\n\n' +
'This PR touches `python/`, `ansible/`, `terraform/`, or ' +
'`go/`, so a populated **Test Report** section is required in the PR ' +
`body. See [TESTING.md](${TESTING_URL}) for what to capture ` +
'(environment, platform version, first-run output, idempotency ' +
'check, teardown).\n\n' +
'This is a soft gate - your CI checks are unaffected - but ' +
'reviewers will not approve until the report is filled in.'
);
}