Skip to content

Commit ac4f746

Browse files
authored
Handle-exceptions-at-the-root-request (#2003)
* Fix bug * Convert exceptions at the root * Adjust to exceptions in platform * Make gitlab groups a list setting * Remove au lowercasing for API errors * Adjust to exceptions in platform * Allow new param as multi-valued * Adjust to QG not found message * Separate exception for HTTP and Connection Error * Simplify import_zip excepton handling * Disallowed to remove admin permission to the admin user * Adjust exception handling * Add illegal issue transition * Add illegal issue transition * Fix * Raise IllegalIssueTransition * Remove Illegal Issue Transition Exception * Remove illegal issue err code * Remove nolint option from the run_scanner call * Formatting * Fix key callection for object not found * Handle ObjectNotFound for import_zip * Remove exception handling in delete() * Fixes on WebHook corner cases * Add auto gen of unsupported operation for bad parameter value * Remove ConnectionError, it's a type of RequestException * Remove useless CLI parameters process * Raise UnsupportedOperationw hen passing a wrong severity * Verify exception when passing incorrect parameters * Verify exception when import non existing project key * Verify exception when webhook with too small secret string * Handle update when reference portfolio already exists * Remove exception handling (handled in Platform) * Add exception handling for incorretc API param value * Quality pass * Add type hints * Quality pass * Quality pass * Fix key retrieval in case of exception * Add message to detect not found * Refactoring * Adjust to 9.9 * Adjust to 9.9 * Use poetry to install dependencies * Fix run_linters * Find ruff from python * Remove python to ruff ruff * Install deps before running linters * Remove install before runninng linters * Try to run linters in same env
1 parent 3c65463 commit ac4f746

File tree

20 files changed

+195
-196
lines changed

20 files changed

+195
-196
lines changed

.github/workflows/build.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@ jobs:
2828
- name: Install dependencies
2929
run: |
3030
python -m pip install --upgrade pip
31-
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
32-
if [ -f requirements-to-build.txt ]; then pip install -r requirements-to-build.txt; fi
31+
python -m pip install poetry
32+
poetry install
33+
- name: Build package
34+
run: |
35+
poetry build
36+
continue-on-error: false
3337
# Linting is done in the run_linters.sh script
3438

3539
- name: Prep tests
@@ -49,9 +53,10 @@ jobs:
4953
- name: Run linters
5054
working-directory: .
5155
run: |
56+
python -m pip install ruff pylint flake8
5257
chmod +x conf/run_linters.sh
5358
conf/run_linters.sh
54-
#- name: Cache SonarQube packages
59+
# - name: Cache SonarQube packages
5560
# uses: actions/cache@v4
5661
# with:
5762
# path: ./.sonar

conf/prep_all_tests.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ function create_fresh_project {
4343

4444
conf/run_linters.sh
4545

46-
create_fresh_project "${SYNC_PROJECT_KEY}" "${SONAR_HOST_URL_TEST:?}" "${SONAR_TOKEN_TEST_ADMIN_USER}" "${SONAR_TOKEN_TEST_ADMIN_ANALYSIS}" -nolint
46+
create_fresh_project "${SYNC_PROJECT_KEY}" "${SONAR_HOST_URL_TEST:?}" "${SONAR_TOKEN_TEST_ADMIN_USER}" "${SONAR_TOKEN_TEST_ADMIN_ANALYSIS}"
4747
create_fresh_project "${SYNC_PROJECT_KEY}" "${SONAR_HOST_URL_LATEST:?}" "${SONAR_TOKEN_LATEST_ADMIN_USER}" "${SONAR_TOKEN_LATEST_ADMIN_ANALYSIS}"
4848
create_fresh_project "${SYNC_PROJECT_KEY}" "${SONAR_HOST_URL_CB:?}" "${SONAR_TOKEN_CB_ADMIN_USER}" "${SONAR_TOKEN_CB_ADMIN_ANALYSIS}"
4949
create_fresh_project "${SYNC_PROJECT_KEY}" "${SONAR_HOST_URL_9:?}" "${SONAR_TOKEN_9_ADMIN_USER}" "${SONAR_TOKEN_9_ADMIN_ANALYSIS}"

conf/run_linters.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ fi
7272
if [[ "${localbuild}" = "true" ]]; then
7373
if [[ "${linters_to_run}" == *"shellcheck"* ]]; then
7474
echo "===> Running shellcheck"
75-
shellcheck $(find "${ROOT_DIR}" . -name '*.sh') \
75+
shellcheck "$(find "${ROOT_DIR}" . -name '*.sh')" \
7676
-s bash -f json | jq | tee "${BUILD_DIR}/shellcheck-report.json" | "${CONF_DIR}"/shellcheck2sonar.py "${external_format}" > "${SHELLCHECK_REPORT}"
7777
[[ ! -s "${SHELLCHECK_REPORT}" ]] && rm -f "${SHELLCHECK_REPORT}"
7878
cat "${BUILD_DIR}/shellcheck-report.json"

migration/build.sh

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,6 @@ while [[ $# -ne 0 ]]; do
2929
nodocker)
3030
build_image=0
3131
;;
32-
pypi)
33-
release=1
34-
;;
35-
dockerhub)
36-
release_docker=1
37-
;;
3832
*)
3933
;;
4034
esac

sonar/components.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ def get_issues(self, filters: types.ApiParams = None) -> dict[str, object]:
118118
"""Returns list of issues for a component, optionally on branches or/and PRs"""
119119
from sonar.issues import search_all
120120

121-
filters = {k: list(set(v) if isinstance(v, (list, set, tuple)) else v) for k, v in (filters or {}).items() if v is not None}
121+
filters = {k: list(set(v)) if isinstance(v, (list, set, tuple)) else v for k, v in (filters or {}).items() if v is not None}
122122
log.info("Searching issues for %s with filters %s", str(self), str(filters))
123123
issue_list = search_all(endpoint=self.endpoint, params=self.api_params() | {"additionalFields": "comments"} | filters)
124124
self.nbr_issues = len(issue_list)

sonar/findings.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import concurrent.futures
2424
from datetime import datetime
2525
from typing import Optional
26+
import re
2627
from http import HTTPStatus
2728
from requests import RequestException
2829
import Levenshtein
@@ -32,6 +33,7 @@
3233
import sonar.platform as pf
3334
from sonar.util import types
3435
from sonar.util import constants as c, issue_defs as idefs
36+
from sonar import exceptions
3537

3638
import sonar.utilities as util
3739
from sonar import projects, rules
@@ -460,6 +462,10 @@ def do_transition(self, transition: str) -> bool:
460462
return self.post("issues/do_transition", {"issue": self.key, "transition": transition}).ok
461463
except (ConnectionError, RequestException) as e:
462464
util.handle_error(e, f"applying transition {transition}", catch_http_statuses=(HTTPStatus.BAD_REQUEST, HTTPStatus.NOT_FOUND))
465+
except exceptions.SonarException as e:
466+
if re.match(r"Transition from state [A-Za-z]+ does not exist", e.message):
467+
raise exceptions.UnsupportedOperation(e.message) from e
468+
raise
463469
return False
464470

465471
def get_branch_and_pr(self, data: types.ApiPayload) -> tuple[Optional[str], Optional[str]]:

sonar/issues.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -294,18 +294,14 @@ def add_comment(self, comment: str) -> bool:
294294
log.debug("Adding comment '%s' to %s", comment, str(self))
295295
try:
296296
r = self.post("issues/add_comment", {"issue": self.key, "text": comment})
297-
except (ConnectionError, requests.RequestException) as e:
297+
except requests.RequestException as e:
298298
util.handle_error(e, "adding comment", catch_all=True)
299299
return False
300300
return r.ok
301301

302302
def __set_severity(self, **params) -> bool:
303-
try:
304-
log.debug("Changing severity of %s from '%s' to '%s'", str(self), self.severity, str(params))
305-
r = self.post("issues/set_severity", {"issue": self.key, **params})
306-
except (ConnectionError, requests.RequestException) as e:
307-
util.handle_error(e, "changing issue severity", catch_all=True)
308-
return False
303+
log.debug("Changing severity of %s from '%s' to '%s'", str(self), self.severity, str(params))
304+
r = self.post("issues/set_severity", {"issue": self.key, **params})
309305
return r.ok
310306

311307
def set_severity(self, severity: str) -> bool:

sonar/permissions/project_permissions.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ def _set_perms(
7272
self.read()
7373
for p in permissions.PERMISSION_TYPES:
7474
to_remove = diff_func(self.permissions.get(p, {}), new_perms.get(p, {}))
75+
if p == "users" and "admin" in to_remove:
76+
# Don't remove admin permission to the admin user, this is not possible anyway
77+
to_remove["admin"] = [v for v in to_remove["admin"] if v != "admin"]
7578
self._post_api(apis["remove"][p], field[p], to_remove, **kwargs)
7679
to_add = diff_func(new_perms.get(p, {}), self.permissions.get(p, {}))
7780
self._post_api(apis["add"][p], field[p], to_add, **kwargs)

sonar/platform.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from http import HTTPStatus
2929
import sys
3030
import os
31+
import re
3132
from typing import Optional
3233
import time
3334
import datetime
@@ -264,9 +265,8 @@ def __run_request(self, request: callable, api: str, params: types.ApiParams = N
264265
headers["Authorization"] = f"Bearer {self.__token}"
265266
if with_org:
266267
params["organization"] = self.organization
267-
req_type, url = "", ""
268+
req_type, url = getattr(request, "__name__", repr(request)).upper(), ""
268269
if log.get_level() <= log.DEBUG:
269-
req_type = getattr(request, "__name__", repr(request)).upper()
270270
url = self.__urlstring(api, params, kwargs.get("data", {}))
271271
log.debug("%s: %s", req_type, url)
272272
kwargs["headers"] = headers
@@ -288,10 +288,24 @@ def __run_request(self, request: callable, api: str, params: types.ApiParams = N
288288
self.local_url = new_url
289289
r.raise_for_status()
290290
except HTTPError as e:
291-
lvl = log.DEBUG if r.status_code in mute else log.ERROR
291+
code = r.status_code
292+
lvl = log.DEBUG if code in mute else log.ERROR
292293
log.log(lvl, "%s (%s request)", util.error_msg(e), req_type)
293-
raise e
294-
except (ConnectionError, RequestException) as e:
294+
err_msg = util.sonar_error(e.response)
295+
err_msg_lower = err_msg.lower()
296+
key = next((params[k] for k in ("key", "project", "component", "componentKey") if k in params), "Unknown")
297+
if any(
298+
msg in err_msg_lower for msg in ("not found", "no quality gate has been found", "does not exist", "could not find")
299+
): # code == HTTPStatus.NOT_FOUND:
300+
raise exceptions.ObjectNotFound(key, err_msg) from e
301+
if any(msg in err_msg_lower for msg in ("already exists", "already been taken")):
302+
raise exceptions.ObjectAlreadyExists(key, err_msg) from e
303+
if re.match(r"(Value of parameter .+ must be one of|No enum constant)", err_msg):
304+
raise exceptions.UnsupportedOperation(err_msg) from e
305+
if any(msg in err_msg_lower for msg in ("insufficient privileges", "insufficient permissions")):
306+
raise exceptions.SonarException(err_msg, errcodes.SONAR_API_AUTHORIZATION) from e
307+
raise exceptions.SonarException(err_msg, errcodes.SONAR_API) from e
308+
except ConnectionError as e:
295309
util.handle_error(e, "")
296310
return r
297311

sonar/portfolios.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ def add_application_branch(self, app_key: str, branch: str = c.DEFAULT_BRANCH) -
545545
self._applications[app_key].append(branch)
546546
return True
547547

548-
def add_subportfolio(self, key: str, name: str = None, by_ref: bool = False) -> object:
548+
def add_subportfolio(self, key: str, name: str = None, by_ref: bool = False) -> Portfolio:
549549
"""Adds a subportfolio to a portfolio, defined by key, name and by reference option"""
550550

551551
log.info("Adding sub-portfolios to %s", str(self))
@@ -644,7 +644,12 @@ def update(self, data: dict[str, str], recurse: bool) -> None:
644644
if subp_data.get("byReference", False):
645645
o_subp = Portfolio.get_object(self.endpoint, key)
646646
if o_subp.key not in key_list:
647-
self.add_subportfolio(o_subp.key, name=o_subp.name, by_ref=True)
647+
try:
648+
self.add_subportfolio(o_subp.key, name=o_subp.name, by_ref=True)
649+
except exceptions.SonarException as e:
650+
# If the exception is that the portfolio already references, just pass
651+
if "already references" not in e.message:
652+
raise
648653
else:
649654
try:
650655
o_subp = Portfolio.get_object(self.endpoint, key)

0 commit comments

Comments
 (0)