2
2
import os
3
3
import sys
4
4
import time
5
+ import io
5
6
from dataclasses import asdict
6
7
from glob import glob
8
+ from io import BytesIO
7
9
from pathlib import PurePath
8
- from typing import BinaryIO , Dict , List , Tuple , Set
10
+ from typing import BinaryIO , Dict , List , Tuple , Set , Union
9
11
import re
10
12
from socketdev import socketdev
11
13
from socketdev .exceptions import APIFailure
24
26
Purl
25
27
)
26
28
from socketsecurity .core .exceptions import APIResourceNotFound
27
- from socketsecurity .core .licenses import Licenses
28
29
from .socket_config import SocketConfig
29
30
from .utils import socket_globs
30
31
import importlib
@@ -278,6 +279,14 @@ def to_case_insensitive_regex(input_string: str) -> str:
278
279
"""
279
280
return '' .join (f'[{ char .lower ()} { char .upper ()} ]' if char .isalpha () else char for char in input_string )
280
281
282
+ @staticmethod
283
+ def empty_head_scan_file () -> list [tuple [str , tuple [str , Union [BinaryIO , BytesIO ]]]]:
284
+ # Create an empty file for when no head full scan so that the diff endpoint can always be used
285
+ empty_file_obj = io .BytesIO (b"" )
286
+ empty_filename = "initial_head_scan"
287
+ empty_full_scan_file = [(empty_filename , (empty_filename , empty_file_obj ))]
288
+ return empty_full_scan_file
289
+
281
290
@staticmethod
282
291
def load_files_for_sending (files : List [str ], workspace : str ) -> List [Tuple [str , Tuple [str , BinaryIO ]]]:
283
292
"""
@@ -311,7 +320,7 @@ def load_files_for_sending(files: List[str], workspace: str) -> List[Tuple[str,
311
320
312
321
return send_files
313
322
314
- def create_full_scan (self , files : List [ str ], params : FullScanParams , has_head_scan : bool = False ) -> FullScan :
323
+ def create_full_scan (self , files : list [ tuple [ str , tuple [ str , BytesIO ]]], params : FullScanParams ) -> FullScan :
315
324
"""
316
325
Creates a new full scan via the Socket API.
317
326
@@ -331,16 +340,60 @@ def create_full_scan(self, files: List[str], params: FullScanParams, has_head_sc
331
340
raise Exception (f"Error creating full scan: { res .message } , status: { res .status } " )
332
341
333
342
full_scan = FullScan (** asdict (res .data ))
334
- if not has_head_scan :
335
- full_scan .sbom_artifacts = self .get_sbom_data (full_scan .id )
336
- full_scan .packages = self .create_packages_dict (full_scan .sbom_artifacts )
337
-
338
343
create_full_end = time .time ()
339
344
total_time = create_full_end - create_full_start
340
345
log .debug (f"New Full Scan created in { total_time :.2f} seconds" )
341
346
342
347
return full_scan
343
348
349
+ def check_full_scans_status (self , head_full_scan_id : str , new_full_scan_id : str ) -> bool :
350
+ is_ready = False
351
+ current_timeout = self .config .timeout
352
+ self .sdk .set_timeout (0.5 )
353
+ try :
354
+ self .sdk .fullscans .stream (self .config .org_slug , head_full_scan_id )
355
+ except Exception :
356
+ log .debug (f"Queued up full scan for processing ({ head_full_scan_id } )" )
357
+
358
+ try :
359
+ self .sdk .fullscans .stream (self .config .org_slug , new_full_scan_id )
360
+ except Exception :
361
+ log .debug (f"Queued up full scan for processing ({ new_full_scan_id } )" )
362
+ self .sdk .set_timeout (current_timeout )
363
+ start_check = time .time ()
364
+ head_is_ready = False
365
+ new_is_ready = False
366
+ while not is_ready :
367
+ head_full_scan_metadata = self .sdk .fullscans .metadata (self .config .org_slug , head_full_scan_id )
368
+ if head_full_scan_metadata :
369
+ head_state = head_full_scan_metadata .get ("scan_state" )
370
+ else :
371
+ head_state = None
372
+ new_full_scan_metadata = self .sdk .fullscans .metadata (self .config .org_slug , new_full_scan_id )
373
+ if new_full_scan_metadata :
374
+ new_state = new_full_scan_metadata .get ("scan_state" )
375
+ else :
376
+ new_state = None
377
+ if head_state and head_state == "resolve" :
378
+ head_is_ready = True
379
+ if new_state and new_state == "resolve" :
380
+ new_is_ready = True
381
+ if head_is_ready and new_is_ready :
382
+ is_ready = True
383
+ current_time = time .time ()
384
+ if current_time - start_check >= self .config .timeout :
385
+ log .debug (
386
+ f"Timeout reached while waiting for full scans to be ready "
387
+ f"({ head_full_scan_id } , { new_full_scan_id } )"
388
+ )
389
+ break
390
+ total_time = time .time () - start_check
391
+ if is_ready :
392
+ log .info (f"Full scans are ready in { total_time :.2f} seconds" )
393
+ else :
394
+ log .warning (f"Full scans are not ready yet ({ head_full_scan_id } , { new_full_scan_id } )" )
395
+ return is_ready
396
+
344
397
def get_full_scan (self , full_scan_id : str ) -> FullScan :
345
398
"""
346
399
Get a FullScan object for an existing full scan including sbom_artifacts and packages.
@@ -403,14 +456,9 @@ def get_package_license_text(self, package: Package) -> str:
403
456
return ""
404
457
405
458
license_raw = package .license
406
- all_licenses = Licenses ()
407
- license_str = Licenses .make_python_safe (license_raw )
408
-
409
- if license_str is not None and hasattr (all_licenses , license_str ):
410
- license_obj = getattr (all_licenses , license_str )
411
- return license_obj .licenseText
412
-
413
- return ""
459
+ data = self .sdk .licensemetadata .post ([license_raw ], {'includetext' : 'true' })
460
+ license_str = data .data [0 ].license if data and len (data ) == 1 else ""
461
+ return license_str
414
462
415
463
def get_repo_info (self , repo_slug : str , default_branch : str = "socket-default-branch" ) -> RepositoryInfo :
416
464
"""
@@ -485,7 +533,7 @@ def update_package_values(pkg: Package) -> Package:
485
533
pkg .url += f"/{ pkg .name } /overview/{ pkg .version } "
486
534
return pkg
487
535
488
- def get_added_and_removed_packages (self , head_full_scan_id : str , new_full_scan : FullScan ) -> Tuple [Dict [str , Package ], Dict [str , Package ]]:
536
+ def get_added_and_removed_packages (self , head_full_scan_id : str , new_full_scan_id : str ) -> Tuple [Dict [str , Package ], Dict [str , Package ]]:
489
537
"""
490
538
Get packages that were added and removed between scans.
491
539
@@ -496,14 +544,11 @@ def get_added_and_removed_packages(self, head_full_scan_id: str, new_full_scan:
496
544
Returns:
497
545
Tuple of (added_packages, removed_packages) dictionaries
498
546
"""
499
- if head_full_scan_id is None :
500
- log .info (f"No head scan found. New scan ID: { new_full_scan .id } " )
501
- return new_full_scan .packages , {}
502
547
503
- log .info (f"Comparing scans - Head scan ID: { head_full_scan_id } , New scan ID: { new_full_scan . id } " )
548
+ log .info (f"Comparing scans - Head scan ID: { head_full_scan_id } , New scan ID: { new_full_scan_id } " )
504
549
diff_start = time .time ()
505
550
try :
506
- diff_report = self .sdk .fullscans .stream_diff (self .config .org_slug , head_full_scan_id , new_full_scan . id , use_types = True ).data
551
+ diff_report = self .sdk .fullscans .stream_diff (self .config .org_slug , head_full_scan_id , new_full_scan_id , use_types = True ).data
507
552
except APIFailure as e :
508
553
log .error (f"API Error: { e } " )
509
554
sys .exit (1 )
@@ -572,22 +617,27 @@ def create_new_diff(
572
617
# Find manifest files
573
618
files = self .find_files (path )
574
619
files_for_sending = self .load_files_for_sending (files , path )
575
- has_head_scan = False
576
620
if not files :
577
621
return Diff (id = "no_diff_id" )
578
622
579
623
try :
580
624
# Get head scan ID
581
625
head_full_scan_id = self .get_head_scan_for_repo (params .repo )
582
- if head_full_scan_id is not None :
583
- has_head_scan = True
584
626
except APIResourceNotFound :
585
627
head_full_scan_id = None
586
628
629
+ if head_full_scan_id is None :
630
+ tmp_params = params
631
+ tmp_params .tmp = True
632
+ tmp_params .set_as_pending_head = False
633
+ tmp_params .make_default_branch = False
634
+ head_full_scan = self .create_full_scan (Core .empty_head_scan_file (), params )
635
+ head_full_scan_id = head_full_scan .id
636
+
587
637
# Create new scan
588
638
try :
589
639
new_scan_start = time .time ()
590
- new_full_scan = self .create_full_scan (files_for_sending , params , has_head_scan )
640
+ new_full_scan = self .create_full_scan (files_for_sending , params )
591
641
new_full_scan .sbom_artifacts = self .get_sbom_data (new_full_scan .id )
592
642
new_scan_end = time .time ()
593
643
log .info (f"Total time to create new full scan: { new_scan_end - new_scan_start :.2f} " )
@@ -600,7 +650,10 @@ def create_new_diff(
600
650
log .error (f"Stack trace:\n { traceback .format_exc ()} " )
601
651
raise
602
652
603
- added_packages , removed_packages = self .get_added_and_removed_packages (head_full_scan_id , new_full_scan )
653
+ scans_ready = self .check_full_scans_status (head_full_scan_id , new_full_scan .id )
654
+ if scans_ready is False :
655
+ log .error (f"Full scans did not complete within { self .config .timeout } seconds" )
656
+ added_packages , removed_packages = self .get_added_and_removed_packages (head_full_scan_id , new_full_scan .id )
604
657
605
658
diff = self .create_diff_report (added_packages , removed_packages )
606
659
0 commit comments