Skip to content

Commit 4a47507

Browse files
committed
improve error messages
1 parent 96e17d9 commit 4a47507

7 files changed

Lines changed: 57 additions & 20 deletions

File tree

src/pysonar_scanner/__main__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,15 @@ def build_api(config: dict[str, any]) -> SonarQubeApi:
7474
return SonarQubeApi(base_urls, token)
7575

7676

77-
def check_version(api):
77+
def check_version(api: SonarQubeApi):
7878
if api.is_sonar_qube_cloud():
7979
return
8080
version = api.get_analysis_version()
8181
if not version.does_support_bootstrapping():
8282
raise SQTooOldException(
83-
f"Only SonarQube versions >= {MIN_SUPPORTED_SQ_VERSION} are supported, but got {version}"
83+
f"This scanner only supports SonarQube versions >= {MIN_SUPPORTED_SQ_VERSION}. \n"
84+
f"The server at {api.base_urls.base_url} is on version {version}\n"
85+
"Please either upgrade your SonarQube server or use the Sonar Scanner CLI (see https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/scanners/sonarscanner/)."
8486
)
8587

8688

src/pysonar_scanner/api.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
#
2020
import typing
2121
from dataclasses import dataclass
22-
from typing import Optional, TypedDict
22+
from typing import NoReturn, Optional, TypedDict
2323

2424
import requests
2525
import requests.auth
@@ -32,7 +32,11 @@
3232
Key,
3333
)
3434
from pysonar_scanner.utils import remove_trailing_slash, OsStr, ArchStr
35-
from pysonar_scanner.exceptions import SonarQubeApiException, InconsistentConfiguration
35+
from pysonar_scanner.exceptions import (
36+
SonarQubeApiException,
37+
InconsistentConfiguration,
38+
SonarQubeApiUnauthroizedException,
39+
)
3640

3741
GLOBAL_SONARCLOUD_URL = "https://sonarcloud.io"
3842
US_SONARCLOUD_URL = "https://sonarqube.us"
@@ -185,6 +189,16 @@ def __init__(self, base_urls: BaseUrls, token: str):
185189
self.base_urls = base_urls
186190
self.auth = BearerAuth(token)
187191

192+
def __raise_exception(self, exception: Exception) -> NoReturn:
193+
if (
194+
isinstance(exception, requests.RequestException)
195+
and exception.response is not None
196+
and exception.response.status_code == 401
197+
):
198+
raise SonarQubeApiUnauthroizedException.create_default(self.base_urls.base_url) from exception
199+
else:
200+
raise SonarQubeApiException("Error while fetching the analysis version") from exception
201+
188202
def is_sonar_qube_cloud(self) -> bool:
189203
return self.base_urls.is_sonar_qube_cloud
190204

@@ -197,7 +211,7 @@ def get_analysis_version(self) -> SQVersion:
197211
res.raise_for_status()
198212
return SQVersion.from_str(res.text)
199213
except requests.RequestException as e:
200-
raise SonarQubeApiException("Error while fetching the analysis version") from e
214+
self.__raise_exception(e)
201215

202216
def get_analysis_engine(self) -> EngineInfo:
203217
try:
@@ -210,7 +224,7 @@ def get_analysis_engine(self) -> EngineInfo:
210224
raise SonarQubeApiException("Invalid response from the server")
211225
return EngineInfo(filename=json["filename"], sha256=json["sha256"])
212226
except requests.RequestException as e:
213-
raise SonarQubeApiException("Error while fetching the analysis engine information") from e
227+
self.__raise_exception(e)
214228

215229
def download_analysis_engine(self, handle: typing.BinaryIO) -> None:
216230
"""
@@ -226,7 +240,7 @@ def download_analysis_engine(self, handle: typing.BinaryIO) -> None:
226240
)
227241
self.__download_file(res, handle)
228242
except requests.RequestException as e:
229-
raise SonarQubeApiException("Error while fetching the analysis engine") from e
243+
self.__raise_exception(e)
230244

231245
def get_analysis_jres(self, os: OsStr, arch: ArchStr) -> list[JRE]:
232246
try:
@@ -241,7 +255,7 @@ def get_analysis_jres(self, os: OsStr, arch: ArchStr) -> list[JRE]:
241255
json_array = res.json()
242256
return [JRE.from_dict(jre) for jre in json_array]
243257
except (requests.RequestException, KeyError) as e:
244-
raise SonarQubeApiException("Error while fetching the analysis version") from e
258+
self.__raise_exception(e)
245259

246260
def download_analysis_jre(self, id: str, handle: typing.BinaryIO) -> None:
247261
"""
@@ -257,7 +271,7 @@ def download_analysis_jre(self, id: str, handle: typing.BinaryIO) -> None:
257271
)
258272
self.__download_file(res, handle)
259273
except requests.RequestException as e:
260-
raise SonarQubeApiException("Error while fetching the JRE") from e
274+
self.__raise_exception(e)
261275

262276
def __download_file(self, res: requests.Response, handle: typing.BinaryIO) -> None:
263277
res.raise_for_status()

src/pysonar_scanner/configuration/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def load(cls) -> dict[str, any]:
3737
# Handle unknown args starting with '-D'
3838
for arg in unknown_args:
3939
if not arg.startswith("-D"):
40-
raise UnexpectedCliArgument(f"Unexpected argument: {arg}")
40+
raise UnexpectedCliArgument(f"Unexpected argument: {arg}\nRun with --help for more information.")
4141
key_value = arg[2:].split("=", 1)
4242
if len(key_value) == 2:
4343
key, value = key_value

src/pysonar_scanner/exceptions.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,28 @@ class MissingPropertyException(Exception):
3535
@staticmethod
3636
def from_missing_keys(*properties: MissingProperty) -> "MissingPropertyException":
3737
missing_properties = ", ".join([f"{prop.property} ({prop.cli_arg})" for prop in properties])
38-
return MissingPropertyException(f"Missing required properties: {missing_properties}")
38+
fix_message = (
39+
"You can provide these properties using one of the following methods:\n"
40+
"- Command line arguments (e.g., --sonar.projectKey=myproject)\n"
41+
"- Environment variables (e.g., SONAR_PROJECTKEY=myproject)\n"
42+
"- Properties file (sonar-project.properties)\n"
43+
"- Project configuration files (e.g., build.gradle, pom.xml)"
44+
)
45+
return MissingPropertyException(f"Missing required properties: {missing_properties}\n\n{fix_message}")
3946

4047

4148
class SonarQubeApiException(Exception):
4249
pass
4350

4451

52+
class SonarQubeApiUnauthroizedException(SonarQubeApiException):
53+
@staticmethod
54+
def create_default(server_url: str) -> "SonarQubeApiUnauthroizedException":
55+
return SonarQubeApiUnauthroizedException(
56+
f'The provided token is invalid for the server at "{server_url}". Please check that both the token and the server URL are correct.'
57+
)
58+
59+
4560
class SQTooOldException(Exception):
4661
pass
4762

@@ -51,7 +66,9 @@ class InconsistentConfiguration(Exception):
5166

5267

5368
class ChecksumException(Exception):
54-
pass
69+
@staticmethod
70+
def create(what: str) -> "ChecksumException":
71+
return ChecksumException(f"Checksum mismatch. The downloaded {what} is corrupted.")
5572

5673

5774
class UnexpectedCliArgument(Exception):
@@ -74,6 +91,10 @@ def log_error(e: Exception):
7491
logger = logging.getLogger()
7592
is_debug_level = logger.getEffectiveLevel() <= logging.DEBUG
7693

77-
logger.error(str(e), exc_info=is_debug_level)
94+
if is_debug_level:
95+
logger.error("The following exception occured while running the analysis", exc_info=True)
96+
else:
97+
logger.error(str(e), exc_info=False)
98+
logger.info("For more details, please enable debug logging by passing the --verbose option.")
7899

79100
return EXCEPTION_RETURN_CODE

src/pysonar_scanner/jre.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,7 @@ def __attempt_provisioning_jre_with_retry(self) -> tuple[JRE, pathlib.Path]:
6969
if jre_and_resolved_path is None:
7070
jre_and_resolved_path = self.__attempt_provisioning_jre()
7171
if jre_and_resolved_path is None:
72-
raise ChecksumException(
73-
f"Failed to download and verify JRE for {self.sonar_scanner_os} and {self.sonar_scanner_arch}"
74-
)
72+
raise ChecksumException.create("JRE")
7573

7674
return jre_and_resolved_path
7775

@@ -129,7 +127,9 @@ def __extract_jre(self, file_path: pathlib.Path, unzip_dir: pathlib.Path):
129127
with tarfile.open(file_path, "r:gz") as tar_ref:
130128
tar_ref.extractall(unzip_dir, filter="data")
131129
else:
132-
raise UnsupportedArchiveFormat(f"Unsupported archive format: {file_path.suffix}")
130+
raise UnsupportedArchiveFormat(
131+
f"Received JRE is packaged as an unsupported archive format: {file_path.suffix}"
132+
)
133133

134134

135135
@dataclass(frozen=True)

src/pysonar_scanner/scannerengine.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def provision(self) -> pathlib.Path:
119119
if scanner_file is not None:
120120
return scanner_file.filepath
121121
else:
122-
raise ChecksumException("Failed to download and verify scanner engine")
122+
raise ChecksumException.create("scanner engine JAR")
123123

124124
def __download_and_verify(self) -> Optional[CacheFile]:
125125
engine_info = self.api.get_analysis_engine()

tests/its/test_minimal.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,6 @@ def test_minimal_project_unexpected_arg(cli: CliClient):
4646

4747

4848
def test_invalid_token(sonarqube_client: SonarQubeClient, cli: CliClient):
49-
process = cli.run_analysis(sources_dir="minimal", token="invalid")
49+
process = cli.run_analysis(params=["--verbose"], sources_dir="minimal", token="invalid")
5050
assert process.returncode == 1, str(process.stdout)
51-
assert "Error while fetching the analysis version" in process.stdout
51+
assert "HTTPError: 401 Client Error" in process.stdout

0 commit comments

Comments
 (0)