Skip to content

Commit 5fe8eb8

Browse files
SCANPY-113 Parse the scanner properties from the command line (#137)
1 parent 830196d commit 5fe8eb8

File tree

3 files changed

+328
-3
lines changed

3 files changed

+328
-3
lines changed

src/pysonar_scanner/configuration.py

Lines changed: 163 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1919
#
2020
import time
21+
import argparse
2122
from dataclasses import dataclass
2223
from enum import Enum
2324
from typing import Optional
@@ -82,7 +83,6 @@ class Sonar:
8283
token: str = ""
8384
host_url: str = ""
8485
region: str = ""
85-
token: str = ""
8686

8787

8888
@dataclass(frozen=True)
@@ -91,4 +91,165 @@ class Configuration:
9191

9292

9393
class ConfigurationLoader:
94-
pass
94+
95+
@classmethod
96+
def initialize_configuration(cls) -> Configuration:
97+
args = cls.__parse_cli_args()
98+
99+
internal = Internal(args.sonar_scanner_internal_dump_to_file, args.sonar_scanner_internal_sq_version)
100+
101+
scanner = Scanner(
102+
os=args.sonar_scanner_os,
103+
arch=args.sonar_scanner_arch,
104+
connect_timeout=args.sonar_scanner_connect_timeout,
105+
socket_timeout=args.sonar_scanner_socket_timeout,
106+
response_timeout=args.sonar_scanner_response_timeout,
107+
truststore_path=args.sonar_scanner_truststore_path,
108+
truststore_password=args.sonar_scanner_truststore_password,
109+
keystore_path=args.sonar_scanner_keystore_path,
110+
keystore_password=args.sonar_scanner_keystore_password,
111+
proxy_host=args.sonar_scanner_proxy_host,
112+
proxy_port=args.sonar_scanner_proxy_port,
113+
proxy_user=args.sonar_scanner_proxy_user,
114+
proxy_password=args.sonar_scanner_proxy_password,
115+
skip_jre_provisioning=args.skip_jre_provisioning,
116+
java_exe_path=args.sonar_scanner_java_exe_path,
117+
java_opts=args.sonar_scanner_java_opts,
118+
sonarcloud_url=args.sonar_scanner_cloud_url,
119+
api_base_url=args.sonar_scanner_api_url,
120+
internal=internal,
121+
)
122+
123+
sonar = Sonar(
124+
scanner=scanner,
125+
verbose=args.verbose,
126+
project_base_dir=args.sonar_project_base_dir,
127+
user_home=args.sonar_user_home,
128+
token=args.token,
129+
host_url=args.sonar_host_url,
130+
region=args.sonar_region,
131+
)
132+
133+
return Configuration(sonar)
134+
135+
@classmethod
136+
def __parse_cli_args(cls) -> argparse.Namespace:
137+
parser = argparse.ArgumentParser(description="Sonar scanner CLI for python")
138+
139+
parser.add_argument(
140+
"-t",
141+
"--token",
142+
"--sonar-token",
143+
type=str,
144+
required=True,
145+
help="Token used to authenticate against the SonarQube Server or SonarQube cloud",
146+
)
147+
parser.add_argument(
148+
"-v", "--verbose", "--sonar-verbose", action="store_true", default=False, help="Increase output verbosity"
149+
)
150+
151+
parser.add_argument(
152+
"--sonar-host-url",
153+
type=str,
154+
default="",
155+
help="SonarQube Server base URL. For example, http://localhost:9000 for a local instance of SonarQube Server",
156+
)
157+
parser.add_argument(
158+
"--sonar-region",
159+
type=str,
160+
default="",
161+
choices=["us"],
162+
help="The region to contact, only for SonarQube Cloud",
163+
)
164+
parser.add_argument("--sonar-user-home", type=str, help="Base sonar directory, ~/.sonar by default")
165+
166+
parser.add_argument(
167+
"--sonar-scanner-cloud-url",
168+
type=str,
169+
default="",
170+
help="SonarQube Cloud base URL, https://sonarcloud.io for example",
171+
)
172+
parser.add_argument(
173+
"--sonar-scanner-api-url",
174+
type=str,
175+
default="",
176+
help="Base URL for all REST-compliant API calls, https://api.sonarcloud.io for example",
177+
)
178+
parser.add_argument(
179+
"--sonar-scanner-os",
180+
type=str,
181+
choices=["windows", "linux", "macos", "alpine"],
182+
help="OS running the scanner",
183+
)
184+
parser.add_argument(
185+
"--sonar-scanner-arch",
186+
type=str,
187+
choices=["x64", "aarch64"],
188+
help="Architecture on which the scanner will be running",
189+
)
190+
191+
parser.add_argument(
192+
"--skip-jre-provisioning",
193+
action="store_true",
194+
help="If provided, the provisioning of the JRE will be skipped",
195+
)
196+
parser.add_argument(
197+
"--sonar-scanner-java-exe-path", type=str, help="If defined, the scanner engine will be run with this JRE"
198+
)
199+
parser.add_argument(
200+
"--sonar-scanner-java-opts", type=str, help="Arguments provided to the JVM when running the scanner"
201+
)
202+
203+
parser.add_argument(
204+
"--sonar-scanner-internal-dump-to-file",
205+
type=str,
206+
help="Filename where the input to the scanner engine will be dumped. Useful for debugging",
207+
)
208+
parser.add_argument(
209+
"--sonar-scanner-internal-sq-version",
210+
type=str,
211+
help="Emulate the result of the call to get SQ server version. Useful for debugging with --sonar-scanner-internal-dump-to-file",
212+
)
213+
214+
parser.add_argument(
215+
"--sonar-scanner-connect-timeout",
216+
type=int,
217+
help="Time period to establish connections with the server (in seconds)",
218+
)
219+
parser.add_argument(
220+
"--sonar-scanner-socket-timeout",
221+
type=int,
222+
help="Maximum time of inactivity between two data packets when exchanging data with the server (in seconds)",
223+
)
224+
parser.add_argument(
225+
"--sonar-scanner-response-timeout",
226+
type=int,
227+
help="Time period required to process an HTTP call: from sending a request to receiving a response (in seconds)",
228+
)
229+
230+
parser.add_argument(
231+
"--sonar-scanner-truststore-path",
232+
type=str,
233+
help="Path to the keystore containing trusted server certificates, used by the Scanner in addition to OS and the built-in certificates",
234+
)
235+
parser.add_argument("--sonar-scanner-truststore-password", type=str, help="Password to access the truststore")
236+
237+
parser.add_argument(
238+
"--sonar-scanner-keystore-path",
239+
type=str,
240+
help="Path to the keystore containing the client certificates used by the scanner. By default, <sonar.userHome>/ssl/keystore.p12",
241+
)
242+
parser.add_argument("--sonar-scanner-keystore-password", type=str, help="Password to access the keystore")
243+
244+
parser.add_argument("--sonar-scanner-proxy-host", type=str, help="Proxy host")
245+
parser.add_argument("--sonar-scanner-proxy-port", type=int, help="Proxy port")
246+
parser.add_argument("--sonar-scanner-proxy-user", type=str, help="Proxy user")
247+
parser.add_argument("--sonar-scanner-proxy-password", type=str, help="Proxy password")
248+
249+
parser.add_argument(
250+
"--sonar-project-base-dir",
251+
type=str,
252+
help="Directory containing the project to be analyzed. Default is the current directory",
253+
)
254+
255+
return parser.parse_args()

tests/test_api.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1919
#
2020
from typing import TypedDict
21-
import unittest
2221

2322
import io
2423

@@ -29,6 +28,8 @@
2928
from tests import sq_api_utils
3029
from tests.sq_api_utils import sq_api_mocker
3130

31+
import unittest
32+
3233

3334
class TestSQVersion(unittest.TestCase):
3435
def test_does_support_bootstrapping(self):

tests/test_configuration.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#
2+
# Sonar Scanner Python
3+
# Copyright (C) 2011-2024 SonarSource SA.
4+
# mailto:info AT sonarsource 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+
# This program is distributed in the hope that it will be useful,
11+
#
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+
import unittest
21+
22+
from unittest.mock import patch
23+
from io import StringIO
24+
25+
from pysonar_scanner.configuration import ConfigurationLoader, Configuration, Scanner, Internal, Sonar
26+
27+
28+
class TestMain(unittest.TestCase):
29+
30+
@patch("sys.argv", ["myscript.py"])
31+
def test_missing_cli_args(self):
32+
with patch("sys.stderr", new=StringIO()) as mock_stderr, self.assertRaises(SystemExit):
33+
ConfigurationLoader.initialize_configuration()
34+
35+
error_output = mock_stderr.getvalue()
36+
self.assertIn("the following arguments are required: -t/--token", error_output)
37+
38+
@patch("sys.argv", ["myscript.py", "--token", "myToken"])
39+
def test_minimal_cli_args(self):
40+
configuration = ConfigurationLoader.initialize_configuration()
41+
expected_internal = Internal()
42+
expected_scanner = Scanner(internal=expected_internal)
43+
expected_sonar = Sonar(scanner=expected_scanner, token="myToken")
44+
expected_configuration = Configuration(sonar=expected_sonar)
45+
self.assertEqual(configuration, expected_configuration)
46+
47+
@patch("sys.argv", ["myscript.py", "-t", "myToken", "-v"])
48+
def test_alternative_cli_args(self):
49+
alternatives = [["-t", "myToken", "-v"], ["--sonar-token", "myToken", "--sonar-verbose"]]
50+
for alternative in alternatives:
51+
with patch("sys.argv", ["myscript.py", *alternative]), patch("sys.stderr", new=StringIO()):
52+
configuration = ConfigurationLoader.initialize_configuration()
53+
expected_internal = Internal()
54+
expected_scanner = Scanner(internal=expected_internal)
55+
expected_sonar = Sonar(scanner=expected_scanner, token="myToken", verbose=True)
56+
expected_configuration = Configuration(sonar=expected_sonar)
57+
self.assertEqual(configuration, expected_configuration)
58+
59+
@patch("sys.argv", ["myscript.py", "-t", "myToken", "--sonar-scanner-os", "windows2"])
60+
def test_impossible_os_choice(self):
61+
with patch("sys.stderr", new=StringIO()) as mock_stderr:
62+
with self.assertRaises(SystemExit):
63+
ConfigurationLoader.initialize_configuration()
64+
65+
error_output = mock_stderr.getvalue()
66+
self.assertIn("""argument --sonar-scanner-os: invalid choice: 'windows2'""", error_output)
67+
68+
@patch(
69+
"sys.argv",
70+
[
71+
"myscript.py",
72+
"-t",
73+
"myToken",
74+
"-v",
75+
"--sonar-host-url",
76+
"mySonarHostUrl",
77+
"--sonar-region",
78+
"us",
79+
"--sonar-user-home",
80+
"mySonarUserHome",
81+
"--sonar-scanner-cloud-url",
82+
"mySonarScannerCloudUrl",
83+
"--sonar-scanner-api-url",
84+
"mySonarScannerApiUrl",
85+
"--sonar-scanner-os",
86+
"windows",
87+
"--sonar-scanner-arch",
88+
"x64",
89+
"--skip-jre-provisioning",
90+
"--sonar-scanner-java-exe-path",
91+
"mySonarScannerJavaExePath",
92+
"--sonar-scanner-java-opts",
93+
"mySonarScannerJavaOpts",
94+
"--sonar-scanner-internal-dump-to-file",
95+
"mySonarScannerInternalDumpToFile",
96+
"--sonar-scanner-internal-sq-version",
97+
"mySonarScannerInternalSqVersion",
98+
"--sonar-scanner-connect-timeout",
99+
"42",
100+
"--sonar-scanner-socket-timeout",
101+
"43",
102+
"--sonar-scanner-response-timeout",
103+
"44",
104+
"--sonar-scanner-truststore-path",
105+
"mySonarScannerTruststorePath",
106+
"--sonar-scanner-truststore-password",
107+
"mySonarScannerTruststorePassword",
108+
"--sonar-scanner-keystore-path",
109+
"mySonarScannerKeystorePath",
110+
"--sonar-scanner-keystore-password",
111+
"mySonarScannerKeystorePassword",
112+
"--sonar-scanner-proxy-host",
113+
"mySonarScannerProxyHost",
114+
"--sonar-scanner-proxy-port",
115+
"45",
116+
"--sonar-scanner-proxy-user",
117+
"mySonarScannerProxyUser",
118+
"--sonar-scanner-proxy-password",
119+
"mySonarScannerProxyPassword",
120+
"--sonar-project-base-dir",
121+
"mySonarProjectBaseDir",
122+
],
123+
)
124+
def test_all_cli_args(self):
125+
configuration = ConfigurationLoader.initialize_configuration()
126+
expected_internal = Internal(
127+
dump_to_file="mySonarScannerInternalDumpToFile", sq_version="mySonarScannerInternalSqVersion"
128+
)
129+
130+
expected_scanner = Scanner(
131+
internal=expected_internal,
132+
os="windows",
133+
arch="x64",
134+
connect_timeout=42,
135+
socket_timeout=43,
136+
response_timeout=44,
137+
truststore_path="mySonarScannerTruststorePath",
138+
truststore_password="mySonarScannerTruststorePassword",
139+
keystore_path="mySonarScannerKeystorePath",
140+
keystore_password="mySonarScannerKeystorePassword",
141+
proxy_host="mySonarScannerProxyHost",
142+
proxy_port=45,
143+
proxy_user="mySonarScannerProxyUser",
144+
proxy_password="mySonarScannerProxyPassword",
145+
skip_jre_provisioning=True,
146+
java_exe_path="mySonarScannerJavaExePath",
147+
java_opts="mySonarScannerJavaOpts",
148+
sonarcloud_url="mySonarScannerCloudUrl",
149+
api_base_url="mySonarScannerApiUrl",
150+
)
151+
152+
expected_sonar = Sonar(
153+
scanner=expected_scanner,
154+
token="myToken",
155+
verbose=True,
156+
host_url="mySonarHostUrl",
157+
region="us",
158+
user_home="mySonarUserHome",
159+
project_base_dir="mySonarProjectBaseDir",
160+
)
161+
162+
expected_configuration = Configuration(sonar=expected_sonar)
163+
self.assertEqual(configuration, expected_configuration)

0 commit comments

Comments
 (0)