-
-
Notifications
You must be signed in to change notification settings - Fork 57
142 lines (133 loc) · 5.69 KB
/
Copy pathsonar-pr-comment.yml
File metadata and controls
142 lines (133 loc) · 5.69 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
name: SonarQube PR insights comment
# Posts a sticky, advisory PR comment summarising the current Sonar
# smells / bugs / vulnerabilities for the project. This is intentionally
# a soft signal: the workflow never sets a failing check and never
# blocks merge. The Sonar scan itself runs on push to main via
# sonar-scan.yml; this comment is the operator-facing surface so a PR
# author can see "we currently sit at N smells" without leaving the PR
# tab.
#
# Skipped on fork PRs (no secret access) and when SONAR_HOST_URL is
# unset (host not configured for this repo). Both cases degrade
# silently rather than failing the workflow.
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
issues: write
pull-requests: write
concurrency:
group: sonar-pr-comment-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
comment:
name: Sonar smells delta comment
runs-on: ubuntu-latest
timeout-minutes: 5
# Skip on forks (no secret access) and when the Sonar host is
# unconfigured for this repo. Both checks keep this advisory and
# avoid noisy failures.
if: >-
${{ vars.SONAR_HOST_URL != '' &&
github.event.pull_request.head.repo.full_name == github.repository }}
steps:
- name: Harden runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
disable-sudo: true
- name: Query Sonar measures
id: query
env:
SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
set -euo pipefail
# Tolerate the case where the project has not been scanned
# yet: this workflow stays advisory either way.
http_code=$(curl -sS -o response.json -w '%{http_code}' \
-u "${SONAR_TOKEN}:" \
"${SONAR_HOST_URL%/}/api/measures/component?component=bernstein&metricKeys=code_smells,bugs,vulnerabilities,security_hotspots,coverage")
echo "status=${http_code}" >> "$GITHUB_OUTPUT"
if [ "${http_code}" != "200" ]; then
echo "Sonar returned ${http_code}; will post a placeholder comment."
echo '{"component":{"measures":[]}}' > response.json
fi
# Extract metrics defensively. ``jq -r`` returns ``null`` when
# absent; we coerce to ``-`` so the comment table never breaks.
extract() {
jq -r --arg k "$1" '
.component.measures
| map(select(.metric == $k))
| .[0].value // "-"
' response.json
}
{
echo "code_smells=$(extract code_smells)"
echo "bugs=$(extract bugs)"
echo "vulnerabilities=$(extract vulnerabilities)"
echo "security_hotspots=$(extract security_hotspots)"
echo "coverage=$(extract coverage)"
} >> "$GITHUB_OUTPUT"
- name: Post or update sticky comment
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
SONAR_STATUS: ${{ steps.query.outputs.status }}
CODE_SMELLS: ${{ steps.query.outputs.code_smells }}
BUGS: ${{ steps.query.outputs.bugs }}
VULNS: ${{ steps.query.outputs.vulnerabilities }}
HOTSPOTS: ${{ steps.query.outputs.security_hotspots }}
COVERAGE: ${{ steps.query.outputs.coverage }}
with:
github-token: ${{ github.token }}
script: |
const marker = '<!-- sonar-pr-comment:smells -->';
const prNumber = Number(process.env.PR_NUMBER);
const status = process.env.SONAR_STATUS || '';
const lines = [
marker,
'## Sonar insights (advisory, no merge-block)',
'',
status === '200'
? 'Snapshot of `bernstein` on the configured Sonar instance:'
: `Sonar query returned status ${status || 'unknown'}; this is advisory only.`,
'',
'| Metric | Value |',
'|---|---|',
`| Coverage | ${process.env.COVERAGE || '-'} |`,
`| Code smells | ${process.env.CODE_SMELLS || '-'} |`,
`| Bugs | ${process.env.BUGS || '-'} |`,
`| Vulnerabilities | ${process.env.VULNS || '-'} |`,
`| Security hotspots | ${process.env.HOTSPOTS || '-'} |`,
'',
'Run `bernstein doctor sonar` locally for the full surface.',
'',
'_This comment is a soft signal. The Sonar scan runs on push to `main`; the PR check itself never fails on smells._',
];
const body = lines.join('\n');
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100,
});
const existing = comments.find((c) => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
core.info(`Updated sticky sonar comment #${existing.id}.`);
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
core.info('Posted sticky sonar comment.');
}