-
Notifications
You must be signed in to change notification settings - Fork 4
138 lines (120 loc) · 6.23 KB
/
explain-failure.yml
File metadata and controls
138 lines (120 loc) · 6.23 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
# © 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: Explain CI failure
# When CI / PR Guard / Validate Examples fails on a PR, post a sticky
# comment with plain-English fix guidance keyed to the failing job.
#
# Runs in the workflow_run context so it has API write permission even
# for fork PRs (the fork's PR-context workflows cannot write back).
# Safe because no untrusted code is checked out — we only read REST API
# metadata and post a comment.
on:
workflow_run:
workflows: ["CI", "PR Guard", "Validate Examples"]
types: [completed]
permissions:
pull-requests: write
actions: read
contents: read
jobs:
explain:
name: Post fix guidance
if: github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
const STICKY_KEY = '<!-- pace:explain-failure -->';
const owner = context.repo.owner;
const repo = context.repo.repo;
const run = context.payload.workflow_run;
const repoUrl = `https://github.com/${owner}/${repo}`;
// Map failing job name -> contributor-facing fix guidance.
// Job names must match the `name:` of each job in the upstream workflows.
const HINTS = {
'validate-and-lint':
'**Ruff lint or format check failed.** Reproduce locally:\n' +
'```bash\n' +
'make lint # see what failed\n' +
'ruff check python/ --fix # auto-fix lint\n' +
'ruff format python/ # auto-fix format\n' +
'```\n' +
'Then commit and push.',
'commitlint':
'**Commit message does not follow Conventional Commits.** Required format:\n' +
'```\n<type>(<scope>): <description>\n```\n' +
'Valid types: `build, chore, ci, doc, feat, fix, perf, refactor, revert, style, test`. ' +
'Valid scopes: `python, ansible, terraform, docs, ci, deps`.\n\n' +
'To rewrite the last commit:\n' +
'```bash\n' +
'git commit --amend -m "feat(python): your description"\n' +
'git push --force-with-lease\n' +
'```',
'secret-scan':
'**TruffleHog flagged a possible leaked credential** in your diff. ' +
'Even if it looks like a placeholder, it can fail when it matches a real secret format.\n\n' +
'1. Replace the value with an obvious placeholder (`changeme`, `<PASSWORD>`).\n' +
'2. **If you ever pushed a real secret, rotate it immediately.** Force-push is not enough — ' +
'the value is in git history on the remote.\n' +
'3. If it is a verified false positive, add the path to `.trufflehogignore`.',
'Validate YAML syntax':
'**A changed YAML file failed parsing.** Open it in your editor — the YAML extension ' +
'highlights the bad line. Common causes: tabs (use spaces), unquoted special characters, ' +
'mismatched indentation.',
'Ansible — syntax & lint':
'**`ansible-playbook --syntax-check` or `ansible-lint` failed.** Reproduce locally:\n' +
'```bash\nmake ansible-lint\n```\n' +
`See [docs/troubleshooting.md](${repoUrl}/blob/main/docs/troubleshooting.md#ansible-issues) for common Ansible errors.`,
'Terraform — fmt, validate & lint':
'**Terraform format, validate, or tflint failed.** Reproduce locally:\n' +
'```bash\nmake terraform-validate\n```\n' +
'Most failures are formatting — fix all of them with:\n' +
'```bash\nterraform fmt -recursive terraform/\n```',
};
const prs = run.pull_requests || [];
if (prs.length === 0) {
core.info('workflow_run has no associated PR; nothing to comment on.');
return;
}
const issue_number = prs[0].number;
const { data: jobsData } = await github.rest.actions.listJobsForWorkflowRun({
owner, repo, run_id: run.id, per_page: 100,
});
const failedJobs = jobsData.jobs
.filter(j => j.conclusion === 'failure')
.map(j => j.name);
if (failedJobs.length === 0) {
core.info('No failed jobs in this run.');
return;
}
const sections = failedJobs.map(name => {
const hint = HINTS[name] ||
`**${name}** failed — open the [run logs](${run.html_url}) to see the cause.`;
return `### ${name}\n\n${hint}`;
});
const body =
`${STICKY_KEY}\n` +
`### A CI check failed — here is how to fix it\n\n` +
`**Workflow:** [${run.name}](${run.html_url}) · ` +
`**Run #${run.run_number}** · ` +
`**Failed jobs:** ${failedJobs.length}\n\n` +
sections.join('\n\n---\n\n') + '\n\n' +
`Push a fix and CI re-runs automatically; this comment updates with the next failure (or stays put if the same check fails again). ` +
`Stuck? Comment on the PR and a maintainer will help — typical response time is 1 business day.\n\n` +
`<sub>Auto-generated · ` +
`[explain-failure.yml](${repoUrl}/blob/main/.github/workflows/explain-failure.yml)</sub>`;
const { data: comments } = await github.rest.issues.listComments({
owner, repo, issue_number, per_page: 100,
});
const existing = comments.find(c => c.body && c.body.includes(STICKY_KEY));
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,
});
}