Repository navigation
324 lines (315 loc) · 12.7 KB
/
Copy pathcode-scan.yml
File metadata and controls
324 lines (315 loc) · 12.7 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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
---
# SPDX-FileCopyrightText: NONE
# SPDX-License-Identifier: CC0-1.0
name: "SAST"
permissions: {}
on:
push:
paths-ignore:
- ".github/**"
- ".gitlab/**"
- ".gitlab-ci.yml"
- ".travis.yml"
branches:
- "main"
jobs:
pre-requisites:
name: "Pre-requisites"
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
dependency-graph-enabled: "${{ steps.dependency-graph.outputs.result }}"
codacy-token-set: "${{ steps.check-tokens.outputs.CODACY_TOKEN_SET }}"
sonar-token-set: "${{ steps.check-tokens.outputs.SONAR_TOKEN_SET }}"
steps:
- name: "Verify tokens"
id: check-tokens
shell: bash
env:
CODACY_PROJECT_TOKEN: "${{ secrets.CODACY_PROJECT_TOKEN }}"
SONAR_TOKEN: "${{ secrets.SONAR_TOKEN }}"
run: |
# Verifying tokens...
# Codacy
if test -n "${CODACY_PROJECT_TOKEN?}"; then token_set='true'; else token_set='false'; fi
printf 'CODACY_TOKEN_SET=%s\n' "${token_set:?}" 1>> "${GITHUB_OUTPUT?}"
# SonarQube
if test -n "${SONAR_TOKEN?}"; then token_set='true'; else token_set='false'; fi
printf 'SONAR_TOKEN_SET=%s\n' "${token_set:?}" 1>> "${GITHUB_OUTPUT?}"
- name: "Verify the dependency graph"
id: dependency-graph
uses: actions/github-script@v9
timeout-minutes: 5
with:
retries: 3
script: |
/* jshint esversion: 11 */
const response = await github.rest.dependencyGraph.exportSbom({
owner: context.repo.owner,
repo: context.repo.repo,
}).catch(response => response);
if(response && response.status === 200) {
console.log('The dependency graph is enabled.');
return true;
} else if(response && response.status === 404) {
console.error('::error::The dependency graph is disabled.');
} else {
let errorMsg = 'exportSbom failed';
if(response && response.status && response.message) errorMsg += ' with error ' + response.status + ' (' + response.message + ')';
throw new Error(errorMsg);
}
return false;
dependency-submission:
name: "Dependency submission"
needs: [pre-requisites]
runs-on: ubuntu-latest
timeout-minutes: 10
if: "${{ needs.pre-requisites.outputs.dependency-graph-enabled == 'true' }}"
permissions:
contents: write
steps:
- name: "Checkout sources"
uses: actions/checkout@v7
with:
lfs: false
submodules: false
fetch-tags: false
- name: "Setup Java"
uses: actions/setup-java@v6
with:
distribution: "temurin"
java-version-file: ".tool-versions"
- name: "Use Gradle wrapper cache"
uses: actions/cache@v6
timeout-minutes: 5
with:
key: "gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}"
path: |
~/.gradle/wrapper
~/.gradle/notifications
enableCrossOsArchive: true
- name: "Generate and submit dependency graph"
id: "submit-deps-1"
continue-on-error: true
uses: gradle/actions/dependency-submission@v6
with:
dependency-graph: "generate-and-submit"
cache-disabled: true
validate-wrappers: true
- name: "Wait before retry (if needed)"
if: "${{ steps.submit-deps-1.outcome == 'failure' }}"
shell: bash
run: "sleep 30"
- name: "Retry: Generate and submit dependency graph (if needed)"
id: "submit-deps-2"
if: "${{ steps.submit-deps-1.outcome == 'failure' }}"
uses: gradle/actions/dependency-submission@v6
with:
dependency-graph: "generate-and-submit"
cache-disabled: true
validate-wrappers: true
codacy:
name: "Codacy"
needs: [pre-requisites]
runs-on: ubuntu-latest
timeout-minutes: 30
if: "${{ needs.pre-requisites.outputs.codacy-token-set == 'true' }}"
concurrency:
group: "${{ github.repository_id }}-${{ github.workflow }}-codacy"
cancel-in-progress: false
permissions:
contents: read # Needed to checkout the repository (only required for private repositories)
security-events: write
env:
CODACY_ANALYSIS_CLI_VERSION: "7.10.1"
DOCKER_IMAGE_PATH: "/tmp/codacy-cli-image.tar"
steps:
- name: "Checkout sources"
uses: actions/checkout@v7
with:
lfs: false
submodules: false
fetch-tags: false
- name: "Use Docker image cache"
id: docker-cache
uses: actions/cache@v6
timeout-minutes: 10
with:
key: "codacy-cli-${{ runner.os }}-${{ env.CODACY_ANALYSIS_CLI_VERSION }}"
path: "${{ env.DOCKER_IMAGE_PATH }}"
- name: "Download Docker image if not cached"
shell: bash
timeout-minutes: 15
if: "${{ steps.docker-cache.outputs.cache-hit != 'true' }}"
run: |
docker pull "codacy/codacy-analysis-cli:${CODACY_ANALYSIS_CLI_VERSION:?}"
docker save "codacy/codacy-analysis-cli:${CODACY_ANALYSIS_CLI_VERSION:?}" -o "${DOCKER_IMAGE_PATH:?}"
- name: "Load Docker image from cache if cached"
shell: bash
timeout-minutes: 10
if: "${{ steps.docker-cache.outputs.cache-hit == 'true' }}"
run: |
docker load -i "${DOCKER_IMAGE_PATH:?}"
rm -f -- "${DOCKER_IMAGE_PATH:?}"
- name: "Codacy analysis"
uses: codacy/codacy-analysis-cli-action@v4
timeout-minutes: 20
env:
# Must be explicitly passed to this step, otherwise the action defaults to its own version
CODACY_ANALYSIS_CLI_VERSION: "${{ env.CODACY_ANALYSIS_CLI_VERSION }}"
with:
project-token: "${{ secrets.CODACY_PROJECT_TOKEN }}"
#verbose: true
output: "results.sarif"
format: "sarif"
# Adjust severity of non-security issues
gh-code-scanning-compat: true
# Force 0 exit code to allow SARIF file generation
# This will hand over control about PR rejection to the GitHub side
max-allowed-issues: 2147483647
upload: false
- name: "Combine all SARIF runs by tool name"
shell: bash
timeout-minutes: 5
run: |
# Combining all SARIF runs by tool name...
jq '.runs |= (
# Shorten tool driver name by replacing " (reported by " with " ("
map(.tool.driver.name |= sub(" \\(reported by "; " (")) |
# Group runs by tool name to create unique categories (Post-March 27, 2026 GitHub requirement)
group_by(.tool.driver.name) |
map(
# Store the group and the first element (as template)
. as $group |
.[0] as $first |
# Rebuild the run object by collecting data in a single pass
$first + {
# Collect all results and artifacts from the group, removing ruleIndex if it is -1
"results": ([$group[].results[] | if .ruleIndex == -1 then del(.ruleIndex) else . end] | unique),
"artifacts": ([$group[].artifacts[]] | unique),
# Keep individual invocations and remove workingDirectory if it matches "file:///codacy"
"invocations": ([$group[].invocations[] | if .workingDirectory.uri == "file:///codacy" then del(.workingDirectory) else . end])
}
)
)' -- './results.sarif' 1> './results-combined.sarif'
- name: "Validate the SARIF file"
uses: microsoft/sarif-actions@v0.2
timeout-minutes: 5
with:
command: "validate ./results-combined.sarif"
- name: "Store the SARIF file"
uses: actions/upload-artifact@v7
timeout-minutes: 10
with:
path: "${{ github.workspace }}/results-combined.sarif"
retention-days: 1
archive: false
overwrite: false
- name: "Upload SARIF results file to GitHub"
uses: github/codeql-action/upload-sarif@v4
timeout-minutes: 10
if: "${{ github.ref == 'refs/heads/main' }}"
with:
token: "${{ github.token }}"
sarif_file: "./results-combined.sarif"
category: "Codacy"
sonarqube:
name: "SonarQube"
needs: [pre-requisites]
runs-on: ubuntu-latest
timeout-minutes: 40
if: "${{ needs.pre-requisites.outputs.sonar-token-set == 'true' }}"
permissions:
contents: read # Needed to checkout the repository (only required for private repositories)
actions: read
checks: read
steps:
- name: "Checkout sources"
uses: actions/checkout@v7
with:
lfs: false
submodules: false
fetch-tags: false
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: "Await the twin coverage workflow"
id: "await-coverage"
uses: actions/github-script@v9
timeout-minutes: 20
with:
script: |
const repo = { owner: context.repo.owner, repo: context.repo.repo };
const sha = context.payload.pull_request?.head.sha || context.sha;
const branch = context.payload.pull_request ? context.payload.pull_request.head.ref : context.ref.replace('refs/heads/', '');
// Search coverage workflow (2 attempts)
let run = null;
for(let i = 0; i < 2; i++) {
const { data: { workflow_runs } } = await github.rest.actions.listWorkflowRuns({
...repo, workflow_id: 'coverage.yml', head_sha: sha, branch, event: context.eventName
});
run = workflow_runs[0]; // Newest matching run is natively at index 0
if(run || i === 1) break;
await new Promise(r => setTimeout(r, 5000));
}
if(!run) {
core.notice('Coverage workflow skipped or not triggered for this event. Proceeding without coverage data.');
core.setOutput('run_id', '');
return;
}
// Polling loop: wait actively until the target workflow run is completed
while(['in_progress', 'queued', 'waiting'].includes(run.status)) {
console.log(`Coverage workflow is ${run.status}. Waiting 30 seconds...`);
await new Promise(r => setTimeout(r, 30000));
run = (await github.rest.actions.getWorkflowRun({ ...repo, run_id: run.id })).data;
}
const ok = run.conclusion === 'success';
if(!ok) core.notice(`Coverage workflow ended with status: ${run.conclusion}. Proceeding without coverage data.`);
core.setOutput('run_id', ok ? run.id : '');
- name: "Get JSON coverage report"
id: "download-coverage"
if: "${{ steps.await-coverage.outputs.run_id != '' }}"
uses: actions/download-artifact@v8
timeout-minutes: 10
with:
github-token: "${{ github.token }}"
run-id: "${{ steps.await-coverage.outputs.run_id }}"
name: "coverage.json"
path: "${{ github.workspace }}/coverage/"
- name: "Convert SimpleCov JSON coverage to SonarQube generic XML coverage"
if: "${{ steps.download-coverage.outcome != 'skipped' }}"
shell: bash
timeout-minutes: 10
run: |
jq -r '
"<coverage version=\"1\">",
(
(.coverage | keys_unsorted[]) as $path |
(.coverage[$path].lines) as $lines |
"<file path=\"\($path)\">",
( range(0; $lines | length) |
$lines[.] as $val |
select($val != null) |
" <lineToCover lineNumber=\"\(.+1)\" covered=\"\(if $val > 0 then "true" else "false" end)\"/>"
),
"</file>"
),
"</coverage>"
' -- "${GITHUB_WORKSPACE:?}/coverage/coverage.json" 1> "${GITHUB_WORKSPACE:?}/coverage/sonar-generic-coverage.xml"
- name: "Avoid SonarQube warning"
shell: bash
run: |
# Avoiding the SonarQube warning...
mv -f -- ./build.gradle ./build-disabled.gradle
- name: "SonarQube scan"
uses: SonarSource/sonarqube-scan-action@v8
timeout-minutes: 10
env:
SONAR_TOKEN: "${{ secrets.SONAR_TOKEN }}"
with:
args: >
${{ steps.download-coverage.outcome != 'skipped' && '-Dsonar.coverageReportPaths=./coverage/sonar-generic-coverage.xml' || '' }}
- name: "Post Avoid SonarQube warning"
shell: bash
if: "always()"
run: |
# Post job cleanup.
test ! -f ./build-disabled.gradle || mv -f -- ./build-disabled.gradle ./build.gradle