17
17
# along with this program; if not, write to the Free Software Foundation,
18
18
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
#
20
+ import json
21
+ import pathlib
22
+ from typing import Optional
23
+
20
24
import pysonar_scanner .api as api
25
+
21
26
from pysonar_scanner .api import SonarQubeApi
22
27
from pysonar_scanner .cache import Cache , CacheFile
23
28
from pysonar_scanner .exceptions import ChecksumException , SQTooOldException
29
+ from pysonar_scanner .configuration import Configuration
30
+ from pysonar_scanner .jre import JREProvisioner , JREResolvedPath , JREResolver
31
+ from subprocess import Popen , PIPE
24
32
25
33
26
34
class ScannerEngineProvisioner :
27
35
def __init__ (self , api : SonarQubeApi , cache : Cache ):
28
36
self .api = api
29
37
self .cache = cache
30
38
31
- def provision (self ) -> None :
32
- if self .__download_and_verify ():
33
- return
39
+ def provision (self ) -> pathlib .Path :
40
+ scanner_file = self .__download_and_verify ()
41
+ if scanner_file is not None :
42
+ return scanner_file .filepath
34
43
# Retry once in case the checksum failed due to the scanner engine being updated between getting the checksum and downloading the jar
35
- if self .__download_and_verify ():
36
- return
44
+ scanner_file = self .__download_and_verify ()
45
+ if scanner_file is not None :
46
+ return scanner_file .filepath
37
47
else :
38
48
raise ChecksumException ("Failed to download and verify scanner engine" )
39
49
40
- def __download_and_verify (self ) -> bool :
50
+ def __download_and_verify (self ) -> Optional [ CacheFile ] :
41
51
engine_info = self .api .get_analysis_engine ()
42
52
cache_file = self .cache .get_file (engine_info .filename , engine_info .sha256 )
43
53
if not cache_file .is_valid ():
44
54
self .__download_scanner_engine (cache_file )
45
- return cache_file .is_valid ()
55
+ return cache_file if cache_file .is_valid () else None
46
56
47
57
def __download_scanner_engine (self , cache_file : CacheFile ) -> None :
48
58
with cache_file .open (mode = "wb" ) as f :
@@ -54,6 +64,46 @@ def __init__(self, api: SonarQubeApi, cache: Cache):
54
64
self .api = api
55
65
self .cache = cache
56
66
67
+ def __fetch_scanner_engine (self ) -> pathlib .Path :
68
+ return ScannerEngineProvisioner (self .api , self .cache ).provision ()
69
+
70
+ def run (self , configuration : Configuration ):
71
+ self .__version_check ()
72
+ jre_path = self .__resolve_jre (configuration )
73
+ scanner_engine_path = self .__fetch_scanner_engine ()
74
+ cmd = self .__build_command (jre_path , scanner_engine_path )
75
+ return self .__execute_scanner_engine (configuration , cmd )
76
+
77
+ def __build_command (self , jre_path : JREResolvedPath , scanner_engine_path : pathlib .Path ) -> list [str ]:
78
+ cmd = []
79
+ cmd .append (jre_path .path )
80
+ cmd .append ("-jar" )
81
+ cmd .append (scanner_engine_path )
82
+ return cmd
83
+
84
+ def __execute_scanner_engine (self , configuration : Configuration , cmd : list [str ]) -> int :
85
+ popen = Popen (cmd , stdout = PIPE , stderr = PIPE , stdin = PIPE )
86
+ outs , _ = popen .communicate (configuration .to_json ().encode ())
87
+ exitcode = popen .wait () # 0 means success
88
+ if exitcode != 0 :
89
+ errors = self .__extract_errors_from_log (outs )
90
+ raise RuntimeError (f"Scan failed with exit code { exitcode } " , errors )
91
+ return exitcode
92
+
93
+ def __extract_errors_from_log (self , outs : str ) -> list [str ]:
94
+ try :
95
+ errors = []
96
+ for line in outs .decode ("utf-8" ).split ("\n " ):
97
+ if line .strip () == "" :
98
+ continue
99
+ out_json = json .loads (line )
100
+ if out_json ["level" ] == "ERROR" :
101
+ errors .append (out_json ["message" ])
102
+ return errors
103
+ except Exception as e :
104
+ print (e )
105
+ return []
106
+
57
107
def __version_check (self ):
58
108
if self .api .is_sonar_qube_cloud ():
59
109
return
@@ -63,5 +113,7 @@ def __version_check(self):
63
113
f"Only SonarQube versions >= { api .MIN_SUPPORTED_SQ_VERSION } are supported, but got { version } "
64
114
)
65
115
66
- def __fetch_scanner_engine (self ):
67
- ScannerEngineProvisioner (self .api , self .cache ).provision ()
116
+ def __resolve_jre (self , configuration : Configuration ) -> JREResolvedPath :
117
+ jre_provisionner = JREProvisioner (self .api , self .cache )
118
+ jre_resolver = JREResolver (configuration , jre_provisionner )
119
+ return jre_resolver .resolve_jre ()
0 commit comments