Skip to content

Commit 5068727

Browse files
authored
Fix-2039 (#2044)
* Bump version * Fixes #2039 * Merge branch 'branch-3.16.x' into fix-2039 * Update doc about release 3.16.1 * Adjust to 3.16.1 * Update to 3.17 * Quality pass
1 parent 4651e56 commit 5068727

File tree

9 files changed

+97
-10
lines changed

9 files changed

+97
-10
lines changed

conf/release.Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,6 @@ RUN pip install --upgrade pip \
3333
USER ${USERNAME}
3434
WORKDIR /home/${USERNAME}
3535

36-
HEALTHCHECK --interval=180s --timeout=5s CMD [ "sonar-tools-help" ]
36+
HEALTHCHECK --interval=180s --timeout=5s CMD [ "sonar-tools" ]
3737

38-
CMD [ "sonar-tools-help" ]
38+
CMD [ "sonar-tools" ]

doc/what-is-new.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Next version
22

3+
# Version 3.16.1
4+
5+
- Patch for [Issue #2039](https://github.com/okorach/sonar-tools/issues/2039) that may affect merely all the Sonar Tools
6+
37
# Version 3.16
48

59
* `sonar-config`:

sonar/errcodes.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,6 @@
6969
OBJECT_NOT_FOUND = 16
7070

7171
SONAR_INTERNAL_ERROR = 17
72+
73+
74+
TOO_MANY_RESULTS = 18

sonar/exceptions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,11 @@ class ConnectionError(SonarException):
7575

7676
def __init__(self, message: str) -> None:
7777
super().__init__(message, errcodes.CONNECTION_ERROR)
78+
79+
80+
class TooManyResults(SonarException):
81+
"""When a call to APIs returns too many results."""
82+
83+
def __init__(self, nbr_results: int, message: str) -> None:
84+
super().__init__(message, errcodes.TOO_MANY_RESULTS)
85+
self.nbr_results = nbr_results

sonar/projects.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
import sonar.platform as pf
4141

4242
from sonar.util import types, cache
43+
from sonar.util import project_utils as putils
44+
4345
from sonar import exceptions, errcodes
4446
from sonar import sqobject, components, qualitygates, qualityprofiles, tasks, settings, webhooks, devops
4547
import sonar.permissions.permissions as perms
@@ -1357,14 +1359,22 @@ def count(endpoint: pf.Platform, params: types.ApiParams = None) -> int:
13571359
def search(endpoint: pf.Platform, params: types.ApiParams = None, threads: int = 8) -> dict[str, Project]:
13581360
"""Searches projects in SonarQube
13591361
1360-
:param endpoint: Reference to the SonarQube platform
1361-
:param params: list of parameters to narrow down the search
1362-
:returns: list of projects
1362+
:param Platform endpoint: Reference to the SonarQube platform
1363+
:param ApiParams params: list of filter parameters to narrow down the search
1364+
:param int threads: Number of threads to use for the search
13631365
"""
13641366
new_params = {} if params is None else params.copy()
1365-
if not endpoint.is_sonarcloud():
1367+
if not endpoint.is_sonarcloud() and not new_params.get("filter", None):
13661368
new_params["filter"] = _PROJECT_QUALIFIER
1367-
return sqobject.search_objects(endpoint=endpoint, object_class=Project, params=new_params, threads=threads)
1369+
try:
1370+
log.info("Searching projects with parameters: %s", str(new_params))
1371+
return sqobject.search_objects(endpoint=endpoint, object_class=Project, params=new_params, threads=threads)
1372+
except exceptions.TooManyResults as e:
1373+
log.warning(e.message)
1374+
filter_1, filter_2 = putils.split_loc_filter(new_params["filter"])
1375+
return search(endpoint, params={**new_params, "filter": filter_1}, threads=threads) | search(
1376+
endpoint, params={**new_params, "filter": filter_2}, threads=threads
1377+
)
13681378

13691379

13701380
def get_list(endpoint: pf.Platform, key_list: types.KeyList = None, threads: int = 8, use_cache: bool = True) -> dict[str, Project]:
@@ -1375,8 +1385,8 @@ def get_list(endpoint: pf.Platform, key_list: types.KeyList = None, threads: int
13751385
:return: the list of all projects
13761386
:rtype: dict{key: Project}
13771387
"""
1378-
with _CLASS_LOCK:
1379-
if key_list is None or len(key_list) == 0 or not use_cache:
1388+
if key_list is None or len(key_list) == 0 or not use_cache:
1389+
with _CLASS_LOCK:
13801390
log.info("Listing projects")
13811391
p_list = dict(sorted(search(endpoint=endpoint, threads=threads).items()))
13821392
return p_list

sonar/sqobject.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,10 @@ def search_objects(endpoint: object, object_class: Any, params: types.ApiParams,
242242
data = __get(endpoint, api, {**new_params, p_field: 1})
243243
nb_pages = utilities.nbr_pages(data, api_version)
244244
nb_objects = max(len(data[returned_field]), utilities.nbr_total_elements(data, api_version))
245+
246+
if nb_objects > c.ELASTIC_MAX_RESULTS:
247+
raise exceptions.TooManyResults(nb_objects, f"Too many {cname}s ({nb_objects}) returned by search")
248+
245249
log.info(
246250
"Searching %d %ss, %d pages of %d elements, %d pages in parallel...",
247251
nb_objects,

sonar/util/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,5 @@
7474

7575
SQS_USERS = "sonar-users" # SonarQube Server users default group name
7676
SQC_USERS = "Members" # SonarQube Cloud users default group name
77+
78+
ELASTIC_MAX_RESULTS = 10000 # ElasticSearch max results limit

sonar/util/project_utils.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#
2+
# sonar-tools
3+
# Copyright (C) 2025 Olivier Korach
4+
# mailto:olivier.korach AT gmail DOT com
5+
#
6+
# This program is free software; you can redistribute it and/or
7+
# modify it under the terms of the GNU Lesser General Public
8+
# License as published by the Free Software Foundation; either
9+
# version 3 of the License, or (at your option) any later version.
10+
#
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
# Lesser General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU Lesser General Public License
17+
# along with this program; if not, write to the Free Software Foundation,
18+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
#
20+
"""Project utility functions"""
21+
22+
import math
23+
24+
25+
def split_loc_filter(loc_filter: str) -> tuple[str, str]:
26+
"""Parses a ncloc filter and returns new filters to split the search"""
27+
__FILTER_AND = " and "
28+
loc_min, loc_max = 0, 100000000
29+
new_filters = []
30+
for f in loc_filter.split(__FILTER_AND):
31+
if f.startswith("ncloc>="):
32+
try:
33+
loc_min = int(f[len("ncloc>=") :])
34+
except ValueError:
35+
pass
36+
elif f.startswith("ncloc>"):
37+
try:
38+
loc_min = int(f[len("ncloc>") :]) + 1
39+
except ValueError:
40+
pass
41+
elif f.startswith("ncloc<="):
42+
try:
43+
loc_max = int(f[len("ncloc<=") :])
44+
except ValueError:
45+
pass
46+
elif f.startswith("ncloc<"):
47+
try:
48+
loc_max = int(f[len("ncloc<") :]) - 1
49+
except ValueError:
50+
pass
51+
else:
52+
new_filters.append(f)
53+
loc_middle = int(math.exp((math.log2(loc_max) - math.log2(max(loc_min, 1))) / 2))
54+
slice1 = __FILTER_AND.join(new_filters + [f"ncloc>={loc_min}", f"ncloc<={loc_middle}"])
55+
slice2 = __FILTER_AND.join(new_filters + [f"ncloc>{loc_middle}", f"ncloc<={loc_max}"])
56+
return slice1, slice2

sonar/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@
2525
"""
2626

2727
PACKAGE_VERSION = "3.17"
28-
MIGRATION_TOOL_VERSION = "0.7"
28+
MIGRATION_TOOL_VERSION = "0.6-snapshot"

0 commit comments

Comments
 (0)