1717# along with this program; if not, write to the Free Software Foundation,
1818# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1919#
20+ import json
21+ import pathlib
22+ from typing import Optional
23+
2024import pysonar_scanner .api as api
25+
2126from pysonar_scanner .api import SonarQubeApi
2227from pysonar_scanner .cache import Cache , CacheFile
2328from 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
2432
2533
2634class ScannerEngineProvisioner :
2735 def __init__ (self , api : SonarQubeApi , cache : Cache ):
2836 self .api = api
2937 self .cache = cache
3038
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
3443 # 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
3747 else :
3848 raise ChecksumException ("Failed to download and verify scanner engine" )
3949
40- def __download_and_verify (self ) -> bool :
50+ def __download_and_verify (self ) -> Optional [ CacheFile ] :
4151 engine_info = self .api .get_analysis_engine ()
4252 cache_file = self .cache .get_file (engine_info .filename , engine_info .sha256 )
4353 if not cache_file .is_valid ():
4454 self .__download_scanner_engine (cache_file )
45- return cache_file .is_valid ()
55+ return cache_file if cache_file .is_valid () else None
4656
4757 def __download_scanner_engine (self , cache_file : CacheFile ) -> None :
4858 with cache_file .open (mode = "wb" ) as f :
@@ -54,6 +64,46 @@ def __init__(self, api: SonarQubeApi, cache: Cache):
5464 self .api = api
5565 self .cache = cache
5666
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+
57107 def __version_check (self ):
58108 if self .api .is_sonar_qube_cloud ():
59109 return
@@ -63,5 +113,7 @@ def __version_check(self):
63113 f"Only SonarQube versions >= { api .MIN_SUPPORTED_SQ_VERSION } are supported, but got { version } "
64114 )
65115
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