22import os
33import sys
44import time
5+ import io
56from dataclasses import asdict
67from glob import glob
8+ from io import BytesIO
79from pathlib import PurePath
8- from typing import BinaryIO , Dict , List , Tuple , Set
10+ from typing import BinaryIO , Dict , List , Tuple , Set , Union
911import re
1012from socketdev import socketdev
1113from socketdev .exceptions import APIFailure
2426 Purl
2527)
2628from socketsecurity .core .exceptions import APIResourceNotFound
27- from socketsecurity .core .licenses import Licenses
2829from .socket_config import SocketConfig
2930from .utils import socket_globs
3031import importlib
@@ -278,6 +279,14 @@ def to_case_insensitive_regex(input_string: str) -> str:
278279 """
279280 return '' .join (f'[{ char .lower ()} { char .upper ()} ]' if char .isalpha () else char for char in input_string )
280281
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+
281290 @staticmethod
282291 def load_files_for_sending (files : List [str ], workspace : str ) -> List [Tuple [str , Tuple [str , BinaryIO ]]]:
283292 """
@@ -311,7 +320,7 @@ def load_files_for_sending(files: List[str], workspace: str) -> List[Tuple[str,
311320
312321 return send_files
313322
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 :
315324 """
316325 Creates a new full scan via the Socket API.
317326
@@ -331,16 +340,60 @@ def create_full_scan(self, files: List[str], params: FullScanParams, has_head_sc
331340 raise Exception (f"Error creating full scan: { res .message } , status: { res .status } " )
332341
333342 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-
338343 create_full_end = time .time ()
339344 total_time = create_full_end - create_full_start
340345 log .debug (f"New Full Scan created in { total_time :.2f} seconds" )
341346
342347 return full_scan
343348
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+
344397 def get_full_scan (self , full_scan_id : str ) -> FullScan :
345398 """
346399 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:
403456 return ""
404457
405458 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
414462
415463 def get_repo_info (self , repo_slug : str , default_branch : str = "socket-default-branch" ) -> RepositoryInfo :
416464 """
@@ -485,7 +533,7 @@ def update_package_values(pkg: Package) -> Package:
485533 pkg .url += f"/{ pkg .name } /overview/{ pkg .version } "
486534 return pkg
487535
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 ]]:
489537 """
490538 Get packages that were added and removed between scans.
491539
@@ -496,14 +544,11 @@ def get_added_and_removed_packages(self, head_full_scan_id: str, new_full_scan:
496544 Returns:
497545 Tuple of (added_packages, removed_packages) dictionaries
498546 """
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 , {}
502547
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 } " )
504549 diff_start = time .time ()
505550 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
507552 except APIFailure as e :
508553 log .error (f"API Error: { e } " )
509554 sys .exit (1 )
@@ -572,22 +617,27 @@ def create_new_diff(
572617 # Find manifest files
573618 files = self .find_files (path )
574619 files_for_sending = self .load_files_for_sending (files , path )
575- has_head_scan = False
576620 if not files :
577621 return Diff (id = "no_diff_id" )
578622
579623 try :
580624 # Get head scan ID
581625 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
584626 except APIResourceNotFound :
585627 head_full_scan_id = None
586628
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+
587637 # Create new scan
588638 try :
589639 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 )
591641 new_full_scan .sbom_artifacts = self .get_sbom_data (new_full_scan .id )
592642 new_scan_end = time .time ()
593643 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(
600650 log .error (f"Stack trace:\n { traceback .format_exc ()} " )
601651 raise
602652
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 )
604657
605658 diff = self .create_diff_report (added_packages , removed_packages )
606659
0 commit comments