22
22
THE SOFTWARE.
23
23
"""
24
24
25
+ import concurrent .futures
26
+ import json
25
27
import os
26
28
import uuid
29
+ from urllib .parse import urlparse
27
30
28
31
import grpc
29
- import json
30
-
31
32
from google .protobuf .json_format import MessageToDict , ParseDict
32
33
from pypac .parser import PACFile
33
34
from pypac .resolver import ProxyResolver
34
- from urllib .parse import urlparse
35
35
36
- from .api .components .v2 .scanoss_components_pb2_grpc import ComponentsStub
37
- from .api .cryptography .v2 .scanoss_cryptography_pb2_grpc import CryptographyStub
38
- from .api .dependencies .v2 .scanoss_dependencies_pb2_grpc import DependenciesStub
39
- from .api .vulnerabilities .v2 .scanoss_vulnerabilities_pb2_grpc import VulnerabilitiesStub
40
- from .api .provenance .v2 .scanoss_provenance_pb2_grpc import ProvenanceStub
41
- from .api .semgrep .v2 .scanoss_semgrep_pb2_grpc import SemgrepStub
42
- from .api .cryptography .v2 .scanoss_cryptography_pb2 import AlgorithmResponse
43
- from .api .dependencies .v2 .scanoss_dependencies_pb2 import DependencyRequest , DependencyResponse
44
- from .api .common .v2 .scanoss_common_pb2 import EchoRequest , EchoResponse , StatusResponse , StatusCode , PurlRequest
45
- from .api .vulnerabilities .v2 .scanoss_vulnerabilities_pb2 import VulnerabilityResponse
46
- from .api .semgrep .v2 .scanoss_semgrep_pb2 import SemgrepResponse
36
+ from . import __version__
37
+ from .api .common .v2 .scanoss_common_pb2 import (
38
+ EchoRequest ,
39
+ EchoResponse ,
40
+ PurlRequest ,
41
+ StatusCode ,
42
+ StatusResponse ,
43
+ )
47
44
from .api .components .v2 .scanoss_components_pb2 import (
48
45
CompSearchRequest ,
49
46
CompSearchResponse ,
50
47
CompVersionRequest ,
51
48
CompVersionResponse ,
52
49
)
50
+ from .api .components .v2 .scanoss_components_pb2_grpc import ComponentsStub
51
+ from .api .cryptography .v2 .scanoss_cryptography_pb2 import AlgorithmResponse
52
+ from .api .cryptography .v2 .scanoss_cryptography_pb2_grpc import CryptographyStub
53
+ from .api .dependencies .v2 .scanoss_dependencies_pb2 import DependencyRequest
54
+ from .api .dependencies .v2 .scanoss_dependencies_pb2_grpc import DependenciesStub
53
55
from .api .provenance .v2 .scanoss_provenance_pb2 import ProvenanceResponse
56
+ from .api .provenance .v2 .scanoss_provenance_pb2_grpc import ProvenanceStub
57
+ from .api .semgrep .v2 .scanoss_semgrep_pb2 import SemgrepResponse
58
+ from .api .semgrep .v2 .scanoss_semgrep_pb2_grpc import SemgrepStub
59
+ from .api .vulnerabilities .v2 .scanoss_vulnerabilities_pb2 import VulnerabilityResponse
60
+ from .api .vulnerabilities .v2 .scanoss_vulnerabilities_pb2_grpc import VulnerabilitiesStub
54
61
from .scanossbase import ScanossBase
55
- from . import __version__
56
62
57
63
DEFAULT_URL = 'https://api.osskb.org' # default free service URL
58
64
DEFAULT_URL2 = 'https://api.scanoss.com' # default premium service URL
59
65
SCANOSS_GRPC_URL = os .environ .get ('SCANOSS_GRPC_URL' ) if os .environ .get ('SCANOSS_GRPC_URL' ) else DEFAULT_URL
60
66
SCANOSS_API_KEY = os .environ .get ('SCANOSS_API_KEY' ) if os .environ .get ('SCANOSS_API_KEY' ) else ''
61
67
68
+ MAX_CONCURRENT_REQUESTS = 5
69
+
62
70
63
71
class ScanossGrpc (ScanossBase ):
64
72
"""
65
73
Client for gRPC functionality
66
74
"""
67
75
68
- def __init__ (
76
+ def __init__ ( # noqa: PLR0913
69
77
self ,
70
78
url : str = None ,
71
79
debug : bool = False ,
@@ -222,31 +230,54 @@ def get_dependencies_json(self, dependencies: dict, depth: int = 1) -> dict:
222
230
:return: Server response or None
223
231
"""
224
232
if not dependencies :
225
- self .print_stderr (f 'ERROR: No message supplied to send to gRPC service.' )
233
+ self .print_stderr ('ERROR: No message supplied to send to gRPC service.' )
226
234
return None
227
- request_id = str (uuid .uuid4 ())
228
- resp : DependencyResponse
229
- try :
230
- files_json = dependencies .get ('files' )
231
- if files_json is None or len (files_json ) == 0 :
232
- self .print_stderr (f'ERROR: No dependency data supplied to send to gRPC service.' )
235
+
236
+ files_json = dependencies .get ('files' )
237
+
238
+ if files_json is None or len (files_json ) == 0 :
239
+ self .print_stderr ('ERROR: No dependency data supplied to send to gRPC service.' )
240
+ return None
241
+
242
+ def process_file (file ):
243
+ request_id = str (uuid .uuid4 ())
244
+ try :
245
+ file_request = {'files' : [file ]}
246
+
247
+ request = ParseDict (file_request , DependencyRequest ())
248
+ request .depth = depth
249
+ metadata = self .metadata [:]
250
+ metadata .append (('x-request-id' , request_id ))
251
+ self .print_debug (f'Sending dependency data for decoration (rqId: { request_id } )...' )
252
+ resp = self .dependencies_stub .GetDependencies (request , metadata = metadata , timeout = self .timeout )
253
+
254
+ return MessageToDict (resp , preserving_proto_field_name = True )
255
+ except Exception as e :
256
+ self .print_stderr (
257
+ f'ERROR: { e .__class__ .__name__ } Problem encountered sending gRPC message (rqId: { request_id } ): { e } '
258
+ )
233
259
return None
234
- request = ParseDict (dependencies , DependencyRequest ()) # Parse the JSON/Dict into the dependency object
235
- request .depth = depth
236
- metadata = self .metadata [:]
237
- metadata .append (('x-request-id' , request_id )) # Set a Request ID
238
- self .print_debug (f'Sending dependency data for decoration (rqId: { request_id } )...' )
239
- resp = self .dependencies_stub .GetDependencies (request , metadata = metadata , timeout = self .timeout )
240
- except Exception as e :
241
- self .print_stderr (
242
- f'ERROR: { e .__class__ .__name__ } Problem encountered sending gRPC message (rqId: { request_id } ): { e } '
243
- )
244
- else :
245
- if resp :
246
- if not self ._check_status_response (resp .status , request_id ):
247
- return None
248
- return MessageToDict (resp , preserving_proto_field_name = True ) # Convert gRPC response to a dictionary
249
- return None
260
+
261
+ all_responses = []
262
+ with concurrent .futures .ThreadPoolExecutor (max_workers = MAX_CONCURRENT_REQUESTS ) as executor :
263
+ future_to_file = {executor .submit (process_file , file ): file for file in files_json }
264
+
265
+ for future in concurrent .futures .as_completed (future_to_file ):
266
+ response = future .result ()
267
+ if response :
268
+ all_responses .append (response )
269
+
270
+ SUCCESS_STATUS = 'SUCCESS'
271
+
272
+ merged_response = {'files' : [], 'status' : {'status' : SUCCESS_STATUS , 'message' : 'Success' }}
273
+ for response in all_responses :
274
+ if response :
275
+ if 'files' in response and len (response ['files' ]) > 0 :
276
+ merged_response ['files' ].append (response ['files' ][0 ])
277
+ # Overwrite the status if the any of the responses was not successful
278
+ if 'status' in response and response ['status' ]['status' ] != SUCCESS_STATUS :
279
+ merged_response ['status' ] = response ['status' ]
280
+ return merged_response
250
281
251
282
def get_crypto_json (self , purls : dict ) -> dict :
252
283
"""
@@ -255,7 +286,7 @@ def get_crypto_json(self, purls: dict) -> dict:
255
286
:return: Server response or None
256
287
"""
257
288
if not purls :
258
- self .print_stderr (f 'ERROR: No message supplied to send to gRPC service.' )
289
+ self .print_stderr ('ERROR: No message supplied to send to gRPC service.' )
259
290
return None
260
291
request_id = str (uuid .uuid4 ())
261
292
resp : AlgorithmResponse
@@ -285,7 +316,7 @@ def get_vulnerabilities_json(self, purls: dict) -> dict:
285
316
:return: Server response or None
286
317
"""
287
318
if not purls :
288
- self .print_stderr (f 'ERROR: No message supplied to send to gRPC service.' )
319
+ self .print_stderr ('ERROR: No message supplied to send to gRPC service.' )
289
320
return None
290
321
request_id = str (uuid .uuid4 ())
291
322
resp : VulnerabilityResponse
@@ -315,7 +346,7 @@ def get_semgrep_json(self, purls: dict) -> dict:
315
346
:return: Server response or None
316
347
"""
317
348
if not purls :
318
- self .print_stderr (f 'ERROR: No message supplied to send to gRPC service.' )
349
+ self .print_stderr ('ERROR: No message supplied to send to gRPC service.' )
319
350
return None
320
351
request_id = str (uuid .uuid4 ())
321
352
resp : SemgrepResponse
@@ -345,7 +376,7 @@ def search_components_json(self, search: dict) -> dict:
345
376
:return: Server response or None
346
377
"""
347
378
if not search :
348
- self .print_stderr (f 'ERROR: No message supplied to send to gRPC service.' )
379
+ self .print_stderr ('ERROR: No message supplied to send to gRPC service.' )
349
380
return None
350
381
request_id = str (uuid .uuid4 ())
351
382
resp : CompSearchResponse
@@ -375,7 +406,7 @@ def get_component_versions_json(self, search: dict) -> dict:
375
406
:return: Server response or None
376
407
"""
377
408
if not search :
378
- self .print_stderr (f 'ERROR: No message supplied to send to gRPC service.' )
409
+ self .print_stderr ('ERROR: No message supplied to send to gRPC service.' )
379
410
return None
380
411
request_id = str (uuid .uuid4 ())
381
412
resp : CompVersionResponse
@@ -404,18 +435,22 @@ def _check_status_response(self, status_response: StatusResponse, request_id: st
404
435
:param status_response: Status Response
405
436
:return: True if successful, False otherwise
406
437
"""
438
+
439
+ SUCCEDED_WITH_WARNINGS_STATUS_CODE = 2
440
+ FAILED_STATUS_CODE = 3
441
+
407
442
if not status_response :
408
443
self .print_stderr (f'Warning: No status response supplied (rqId: { request_id } ). Assuming it was ok.' )
409
444
return True
410
445
self .print_debug (f'Checking response status (rqId: { request_id } ): { status_response } ' )
411
446
status_code : StatusCode = status_response .status
412
447
if status_code > 1 :
413
448
ret_val = False # default to failed
414
- msg = " Unsuccessful"
415
- if status_code == 2 :
416
- msg = " Succeeded with warnings"
449
+ msg = ' Unsuccessful'
450
+ if status_code == SUCCEDED_WITH_WARNINGS_STATUS_CODE :
451
+ msg = ' Succeeded with warnings'
417
452
ret_val = True # No need to fail as it succeeded with warnings
418
- elif status_code == 3 :
453
+ elif status_code == FAILED_STATUS_CODE :
419
454
msg = 'Failed with warnings'
420
455
self .print_stderr (f'{ msg } (rqId: { request_id } - status: { status_code } ): { status_response .message } ' )
421
456
return ret_val
@@ -428,10 +463,10 @@ def _get_proxy_config(self):
428
463
:param self:
429
464
"""
430
465
if self .grpc_proxy :
431
- self .print_debug (f 'Setting GRPC (grpc_proxy) proxy...' )
466
+ self .print_debug ('Setting GRPC (grpc_proxy) proxy...' )
432
467
os .environ ['grpc_proxy' ] = self .grpc_proxy
433
468
elif self .proxy :
434
- self .print_debug (f 'Setting GRPC (http_proxy/https_proxy) proxies...' )
469
+ self .print_debug ('Setting GRPC (http_proxy/https_proxy) proxies...' )
435
470
os .environ ['http_proxy' ] = self .proxy
436
471
os .environ ['https_proxy' ] = self .proxy
437
472
elif self .pac :
@@ -450,7 +485,7 @@ def get_provenance_json(self, purls: dict) -> dict:
450
485
:return: Server response or None
451
486
"""
452
487
if not purls :
453
- self .print_stderr (f 'ERROR: No message supplied to send to gRPC service.' )
488
+ self .print_stderr ('ERROR: No message supplied to send to gRPC service.' )
454
489
return None
455
490
request_id = str (uuid .uuid4 ())
456
491
resp : ProvenanceResponse
@@ -461,15 +496,18 @@ def get_provenance_json(self, purls: dict) -> dict:
461
496
self .print_debug (f'Sending data for provenance decoration (rqId: { request_id } )...' )
462
497
resp = self .provenance_stub .GetComponentProvenance (request , metadata = metadata , timeout = self .timeout )
463
498
except Exception as e :
464
- self .print_stderr (f'ERROR: { e .__class__ .__name__ } Problem encountered sending gRPC message '
465
- f'(rqId: { request_id } ): { e } ' )
499
+ self .print_stderr (
500
+ f'ERROR: { e .__class__ .__name__ } Problem encountered sending gRPC message (rqId: { request_id } ): { e } '
501
+ )
466
502
else :
467
503
if resp :
468
504
if not self ._check_status_response (resp .status , request_id ):
469
505
return None
470
506
resp_dict = MessageToDict (resp , preserving_proto_field_name = True ) # Convert gRPC response to a dict
471
507
return resp_dict
472
508
return None
509
+
510
+
473
511
#
474
512
# End of ScanossGrpc Class
475
513
#
0 commit comments