4444from vulnerabilities .models import VulnerabilityReference
4545from vulnerabilities .models import VulnerabilitySeverity
4646from vulnerabilities .models import Weakness
47+ from vulnerabilities .tasks import enqueue_ad_hoc_pipeline
4748from vulnerabilities .throttling import PermissionBasedUserRateThrottle
4849
4950
@@ -1300,8 +1301,7 @@ def lookup(self, request):
13001301
13011302
13021303class LiveEvaluationSerializer (serializers .Serializer ):
1303- purl_string = serializers .CharField (help_text = "PackageURL to evaluate" )
1304- no_threading = serializers .BooleanField (required = False , default = False )
1304+ purl = serializers .CharField (help_text = "PackageURL to evaluate" )
13051305
13061306
13071307class LiveEvaluationViewSet (viewsets .GenericViewSet ):
@@ -1310,7 +1310,7 @@ class LiveEvaluationViewSet(viewsets.GenericViewSet):
13101310 @extend_schema (
13111311 request = LiveEvaluationSerializer ,
13121312 responses = {
1313- 202 : {"description" : "Live evaluation done successfully" },
1313+ 202 : {"description" : "Live evaluation enqueued successfully; returns Run IDs " },
13141314 400 : {"description" : "Invalid request" },
13151315 500 : {"description" : "Internal server error" },
13161316 },
@@ -1324,8 +1324,7 @@ def evaluate(self, request):
13241324 status = status .HTTP_400_BAD_REQUEST ,
13251325 )
13261326
1327- purl_string = serializer .validated_data .get ("purl_string" )
1328- no_threading = serializer .validated_data .get ("no_threading" , False )
1327+ purl_string = serializer .validated_data .get ("purl" )
13291328
13301329 try :
13311330 purl = PackageURL .from_string (purl_string ) if purl_string else None
@@ -1349,31 +1348,78 @@ def evaluate(self, request):
13491348 status = status .HTTP_400_BAD_REQUEST ,
13501349 )
13511350
1352- results = []
1351+ # Create a single LivePipelineRun to represent this evaluation
1352+ from vulnerabilities .models import LivePipelineRun
13531353
1354- def run_importer (importer ):
1354+ live_run = LivePipelineRun .objects .create (purl = purl_string )
1355+ runs = []
1356+ for importer in importers :
13551357 importer_name = getattr (importer , "pipeline_id" , importer .__name__ )
1356- response_data = {"importer" : importer_name , "purl" : purl_string , "steps_completed" : []}
1358+ run_id = enqueue_ad_hoc_pipeline (importer_name , inputs = {"purl" : purl })
1359+ # Attach each PipelineRun to the LivePipelineRun
1360+ from vulnerabilities .models import PipelineRun
1361+
13571362 try :
1358- pipeline_instance = importer (purl = purl )
1359- status_code , error = pipeline_instance .execute ()
1360- if status_code != 0 :
1361- response_data ["error" ] = f"Importer { importer_name } failed: { error } "
1362- else :
1363- response_data ["steps_completed" ].append ("import" )
1364- except Exception as e :
1365- response_data ["error" ] = f"Error running importer { importer_name } : { str (e )} "
1366- return response_data
1367-
1368- if not no_threading and len (importers ) > 1 :
1369- with ThreadPoolExecutor (max_workers = len (importers )) as executor :
1370- future_to_importer = {
1371- executor .submit (run_importer , importer ): importer for importer in importers
1363+ run_obj = PipelineRun .objects .get (run_id = run_id )
1364+ run_obj .live_pipeline = live_run
1365+ run_obj .save ()
1366+ except PipelineRun .DoesNotExist :
1367+ pass
1368+ runs .append (
1369+ {
1370+ "importer" : importer_name ,
1371+ "run_id" : str (run_id ) if run_id else None ,
13721372 }
1373- for future in as_completed (future_to_importer ):
1374- results .append (future .result ())
1375- else :
1376- for importer in importers :
1377- results .append (run_importer (importer ))
1373+ )
1374+ return Response (
1375+ {"live_run_id" : str (live_run .run_id ), "runs" : runs }, status = status .HTTP_202_ACCEPTED
1376+ )
13781377
1379- return Response (results , status = status .HTTP_202_ACCEPTED )
1378+ @extend_schema (
1379+ parameters = [
1380+ OpenApiParameter (
1381+ name = "live_run_id" ,
1382+ description = "UUID of the live run to check status for" ,
1383+ required = True ,
1384+ type = {"type" : "string" },
1385+ location = OpenApiParameter .PATH ,
1386+ )
1387+ ],
1388+ responses = {200 : "LivePipelineRun status and importers status" },
1389+ )
1390+ @action (detail = False , methods = ["get" ], url_path = r"status/(?P<live_run_id>[0-9a-f\-]{36})" )
1391+ def status (self , request , live_run_id = None ):
1392+ from vulnerabilities .models import LivePipelineRun
1393+ from vulnerabilities .models import PipelineRun
1394+
1395+ try :
1396+ live_run = LivePipelineRun .objects .get (run_id = live_run_id )
1397+ except LivePipelineRun .DoesNotExist :
1398+ return Response ({"detail" : "Live run not found." }, status = status .HTTP_404_NOT_FOUND )
1399+
1400+ live_run .update_status ()
1401+
1402+ # Gather status for each importer run
1403+ importer_statuses = []
1404+ for run in live_run .pipelineruns .all ():
1405+ importer_statuses .append (
1406+ {
1407+ "importer" : run .pipeline .pipeline_id ,
1408+ "run_id" : str (run .run_id ),
1409+ "status" : run .status ,
1410+ "run_start_date" : run .run_start_date ,
1411+ "run_end_date" : run .run_end_date ,
1412+ "run_exitcode" : run .run_exitcode ,
1413+ "run_output" : run .run_output ,
1414+ }
1415+ )
1416+
1417+ response = {
1418+ "live_run_id" : str (live_run .run_id ),
1419+ "overall_status" : live_run .status ,
1420+ "created_date" : live_run .created_date ,
1421+ "completed_date" : live_run .completed_date ,
1422+ "purl" : live_run .purl ,
1423+ "importers" : importer_statuses ,
1424+ }
1425+ return Response (response )
0 commit comments