Skip to content

Commit 6ecd0f4

Browse files
authored
Prepare release 4.12.4 (#1282)
1 parent 1cc7ac0 commit 6ecd0f4

7 files changed

Lines changed: 164 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
* Fix branch mapping logic to ensure correct environment assignment ([#1263](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1263))
1212
* Fix Nexus not accepting npm artifacts when uploaded through odsComponentStageUploadToNexus ([#1268](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1268))
1313
* Aqua Stage is being skipped when branches are not eligable ([#1170](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1170))
14+
15+
## [4.12.4] - 2026-06-29
16+
### Changed
1417
* Tune SonarQube scan to work at scale ([#1275](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1275))
1518

1619
## [4.12.3] - 2026-06-19

docs/modules/jenkins-shared-library/partials/odsComponentStageScanWithSonar.adoc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ _List<String>_
4545
all other editions (which are capable of handling multiple branches).
4646

4747

48+
| *filesizeLimit* +
49+
_int_
50+
|Sets the limit in megabytes (MB) for files to be discarded from the analysis scope
51+
if the file size is greater than specified.
52+
Defaults to 2 MB.
53+
54+
4855
| *longLivedBranches* +
4956
_List<String>_
5057
|Branch(es) for which no PR analysis should be performed. If not set, it
@@ -70,6 +77,13 @@ _String_
7077
to be the same as in BuildOpenShiftImageStage.
7178

7279

80+
| *scanTimeout* +
81+
_int_
82+
|Timeout in minutes for the SonarQube scanner execution.
83+
When reached, the scan is aborted but the pipeline continues.
84+
Defaults to 10.
85+
86+
7387
| *sonarQubeNexusRepository* +
7488
_String_
7589
|Nexus repository to upload the SonarQube report to.

src/org/ods/component/ScanWithSonarOptions.groovy

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,16 @@ class ScanWithSonarOptions extends Options {
5454
*/
5555
String sonarQubeNexusRepository
5656

57+
/**
58+
* Timeout in minutes for the SonarQube scanner execution.
59+
* When reached, the scan is aborted but the pipeline continues.
60+
* Defaults to 10. */
61+
int scanTimeout
62+
63+
/**
64+
* Sets the limit in megabytes (MB) for files to be discarded from the analysis scope
65+
* if the file size is greater than specified.
66+
* Defaults to 2 MB. */
67+
int filesizeLimit
68+
5769
}

src/org/ods/component/ScanWithSonarStage.groovy

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ class ScanWithSonarStage extends Stage {
6666
if (!config.containsKey('requireQualityGatePass')) {
6767
config.requireQualityGatePass = false
6868
}
69+
if (!config.containsKey('scanTimeout')) {
70+
config.scanTimeout = 10
71+
}
72+
if (!config.containsKey('filesizeLimit')) {
73+
config.filesizeLimit = 2
74+
}
6975
if (configurationSonarCluster['nexusRepository']) {
7076
config.sonarQubeNexusRepository = configurationSonarCluster['nexusRepository']
7177
}
@@ -102,6 +108,7 @@ class ScanWithSonarStage extends Stage {
102108
sonarProperties['sonar.projectKey'] = sonarProjectKey
103109
sonarProperties['sonar.projectName'] = sonarProjectKey
104110
sonarProperties['sonar.branch.name'] = context.gitBranch
111+
sonarProperties['sonar.filesize.limit'] = options.filesizeLimit
105112

106113
def pullRequestInfo = assemblePullRequestInfo()
107114
def ocSecretName = "sonarqube-private-token"
@@ -119,21 +126,35 @@ class ScanWithSonarStage extends Stage {
119126
)
120127
]) {
121128
logger.info("SonarQube private projects enabled, using private token.")
129+
executeWithTimeout {
130+
runSonarQubeScanAndReport(
131+
sonarProjectKey,
132+
sonarProperties,
133+
pullRequestInfo,
134+
steps.env.privateToken as String
135+
)
136+
}
137+
}
138+
} else {
139+
logger.info("SonarQube private projects disabled, using public token.")
140+
executeWithTimeout {
122141
runSonarQubeScanAndReport(
123142
sonarProjectKey,
124143
sonarProperties,
125144
pullRequestInfo,
126-
steps.env.privateToken as String
145+
""
127146
)
128147
}
129-
} else {
130-
logger.info("SonarQube private projects disabled, using public token.")
131-
runSonarQubeScanAndReport(
132-
sonarProjectKey,
133-
sonarProperties,
134-
pullRequestInfo,
135-
""
136-
)
148+
}
149+
}
150+
151+
private void executeWithTimeout(Closure closure) {
152+
try {
153+
steps.timeout(time: options.scanTimeout, unit: 'MINUTES') {
154+
closure()
155+
}
156+
} catch (Exception e) {
157+
logger.warn "SonarQube scan timed out after ${options.scanTimeout} minutes. Continuing pipeline."
137158
}
138159
}
139160

src/org/ods/services/SonarQubeService.groovy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class SonarQubeService {
5656
'-Dsonar.scm.provider=git',
5757
"-Dsonar.projectKey=${properties['sonar.projectKey']}",
5858
"-Dsonar.projectName=${properties['sonar.projectName']}",
59+
"-Dsonar.filesize.limit=${properties['sonar.filesize.limit']}",
5960
"-Dsonar.sources=.",
6061
]
6162
if (exclusions?.trim()) {

test/groovy/org/ods/component/ScanWithSonarStageSpec.groovy

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,4 +296,72 @@ class ScanWithSonarStageSpec extends PipelineSpockTestBase {
296296
stage.sonarQubeProjectsPrivate == true
297297
}
298298

299+
def "stage uses default scanTimeout and filesizeLimit values"() {
300+
given:
301+
def tempFolderPath = tempFolder.getRoot().absolutePath
302+
def script = Spy(PipelineSteps)
303+
script.env.WORKSPACE = tempFolderPath
304+
def logger = Spy(new Logger(script, false))
305+
def config = [:]
306+
def configurationSonarCluster = [:]
307+
def stage = new ScanWithSonarStage(
308+
script,
309+
new Context(script, [
310+
componentId: "component1",
311+
projectId: "prj1",
312+
buildUrl: "http://build",
313+
buildNumber: "56",
314+
repoName: "component1",
315+
gitCommit: "12112121212121",
316+
cdProject: "prj1-cd",
317+
credentialsId: "cd-user",
318+
branchToEnvironmentMapping: ['*': 'dev']
319+
], logger),
320+
config,
321+
Spy(new BitbucketService(script, 'https://bitbucket.example.com', 'FOO', 'foo-cd-cd-user-with-password', logger)),
322+
Spy(new SonarQubeService(script, logger, "SonarServerConfig")),
323+
Spy(new NexusService("http://nexus", script, "foo-cd-cd-user-with-password")),
324+
logger,
325+
configurationSonarCluster,
326+
[:]
327+
)
328+
expect:
329+
stage.options.scanTimeout == 10
330+
stage.options.filesizeLimit == 2
331+
}
332+
333+
def "stage uses custom scanTimeout and filesizeLimit values"() {
334+
given:
335+
def tempFolderPath = tempFolder.getRoot().absolutePath
336+
def script = Spy(PipelineSteps)
337+
script.env.WORKSPACE = tempFolderPath
338+
def logger = Spy(new Logger(script, false))
339+
def config = [scanTimeout: 30, filesizeLimit: 5]
340+
def configurationSonarCluster = [:]
341+
def stage = new ScanWithSonarStage(
342+
script,
343+
new Context(script, [
344+
componentId: "component1",
345+
projectId: "prj1",
346+
buildUrl: "http://build",
347+
buildNumber: "56",
348+
repoName: "component1",
349+
gitCommit: "12112121212121",
350+
cdProject: "prj1-cd",
351+
credentialsId: "cd-user",
352+
branchToEnvironmentMapping: ['*': 'dev']
353+
], logger),
354+
config,
355+
Spy(new BitbucketService(script, 'https://bitbucket.example.com', 'FOO', 'foo-cd-cd-user-with-password', logger)),
356+
Spy(new SonarQubeService(script, logger, "SonarServerConfig")),
357+
Spy(new NexusService("http://nexus", script, "foo-cd-cd-user-with-password")),
358+
logger,
359+
configurationSonarCluster,
360+
[:]
361+
)
362+
expect:
363+
stage.options.scanTimeout == 30
364+
stage.options.filesizeLimit == 5
365+
}
366+
299367
}

test/groovy/org/ods/services/SonarQubeServiceSpec.groovy

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,15 @@ class SonarQubeServiceSpec extends PipelineSpockTestBase {
8787
logger.debugMode >> false
8888
def service = new SonarQubeService(steps, logger, "env")
8989
def optionsWithPrivateToken = [
90-
properties: ['sonar.projectKey': 'key', 'sonar.projectName': 'name'],
90+
properties: ['sonar.projectKey': 'key', 'sonar.projectName': 'name', 'sonar.filesize.limit': '2'],
9191
gitCommit: "abcdef123456",
9292
pullRequestInfo: [:],
9393
sonarQubeEdition: "community",
9494
exclusions: "test/**",
9595
privateToken: "my-private-token"
9696
]
9797
def optionsWithoutPrivateToken = [
98-
properties: ['sonar.projectKey': 'key', 'sonar.projectName': 'name'],
98+
properties: ['sonar.projectKey': 'key', 'sonar.projectName': 'name', 'sonar.filesize.limit': '2'],
9999
gitCommit: "abcdef123456",
100100
pullRequestInfo: [:],
101101
sonarQubeEdition: "community",
@@ -109,8 +109,8 @@ class SonarQubeServiceSpec extends PipelineSpockTestBase {
109109

110110
then:
111111
2 * steps.sh(label: 'Set Java 17 for SonarQube scan', script: "source use-j17.sh")
112-
1 * steps.sh(label: 'Run SonarQube scan', script: { it.contains("/opt/sonar-scanner/bin/sonar-scanner") && it.contains("-Dsonar.projectKey=key") && it.contains("-Dsonar.projectName=name") && it.contains("-Dsonar.exclusions=test/**") && it.contains("-Dsonar.auth.token=my-private-token") })
113-
1 * steps.sh(label: 'Run SonarQube scan', script: { it.contains("/opt/sonar-scanner/bin/sonar-scanner") && it.contains("-Dsonar.projectKey=key") && it.contains("-Dsonar.projectName=name") && it.contains("-Dsonar.exclusions=test/**") && it.contains("-Dsonar.auth.token=public-token") })
112+
1 * steps.sh(label: 'Run SonarQube scan', script: { it.contains("/opt/sonar-scanner/bin/sonar-scanner") && it.contains("-Dsonar.projectKey=key") && it.contains("-Dsonar.projectName=name") && it.contains("-Dsonar.filesize.limit=2") && it.contains("-Dsonar.exclusions=test/**") && it.contains("-Dsonar.auth.token=my-private-token") })
113+
1 * steps.sh(label: 'Run SonarQube scan', script: { it.contains("/opt/sonar-scanner/bin/sonar-scanner") && it.contains("-Dsonar.projectKey=key") && it.contains("-Dsonar.projectName=name") && it.contains("-Dsonar.filesize.limit=2") && it.contains("-Dsonar.exclusions=test/**") && it.contains("-Dsonar.auth.token=public-token") })
114114
}
115115

116116
def "generateReport calls script.sh with correct params and uses correct token"() {
@@ -209,6 +209,38 @@ class SonarQubeServiceSpec extends PipelineSpockTestBase {
209209
service.generateAndStoreSonarQubeToken("credId", "namespace", "secretName") == ""
210210
}
211211

212+
def "scan includes sonar.filesize.limit parameter from properties"() {
213+
given:
214+
def steps = GroovyMock(util.PipelineSteps)
215+
steps.tool('SonarScanner') >> '/opt/sonar-scanner'
216+
steps.withSonarQubeEnv(_, _) >> { String env, Closure block -> block() }
217+
steps.getSONAR_HOST_URL() >> 'https://sonarqube.example.com'
218+
steps.getSONAR_AUTH_TOKEN() >> 'public-token'
219+
steps.sh(_) >> { Map args ->
220+
if (args.returnStatus) {
221+
return 1
222+
}
223+
return null
224+
}
225+
def logger = Mock(org.ods.util.ILogger)
226+
logger.debugMode >> false
227+
def service = new SonarQubeService(steps, logger, "env")
228+
def options = [
229+
properties: ['sonar.projectKey': 'key', 'sonar.projectName': 'name', 'sonar.filesize.limit': '5'],
230+
gitCommit: "abcdef123456",
231+
pullRequestInfo: [:],
232+
sonarQubeEdition: "community",
233+
exclusions: "",
234+
privateToken: ""
235+
]
236+
237+
when:
238+
service.scan(options)
239+
240+
then:
241+
1 * steps.sh(label: 'Run SonarQube scan', script: { it.contains("-Dsonar.filesize.limit=5") })
242+
}
243+
212244
def "getScannerBinary returns tool path if which fails"() {
213245
given:
214246
def steps = GroovyMock(util.PipelineSteps)

0 commit comments

Comments
 (0)