|
8 | 8 | # |
9 | 9 |
|
10 | 10 |
|
| 11 | +from concurrent.futures import ThreadPoolExecutor |
| 12 | +from concurrent.futures import as_completed |
| 13 | + |
11 | 14 | from django.db.models import Prefetch |
12 | 15 | from django_filters import rest_framework as filters |
13 | 16 | from drf_spectacular.utils import OpenApiParameter |
|
25 | 28 | from rest_framework.reverse import reverse |
26 | 29 | from rest_framework.throttling import AnonRateThrottle |
27 | 30 |
|
| 31 | +from vulnerabilities.importers import LIVE_IMPORTERS_REGISTRY |
28 | 32 | from vulnerabilities.models import AdvisoryReference |
29 | 33 | from vulnerabilities.models import AdvisorySeverity |
30 | 34 | from vulnerabilities.models import AdvisoryV2 |
@@ -1225,3 +1229,83 @@ def lookup(self, request): |
1225 | 1229 | return Response( |
1226 | 1230 | AdvisoryPackageV2Serializer(qs, many=True, context={"request": request}).data |
1227 | 1231 | ) |
| 1232 | + |
| 1233 | + |
| 1234 | +class LiveEvaluationSerializer(serializers.Serializer): |
| 1235 | + purl_string = serializers.CharField(help_text="PackageURL to evaluate") |
| 1236 | + no_threading = serializers.BooleanField(required=False, default=False) |
| 1237 | + |
| 1238 | + |
| 1239 | +class LiveEvaluationViewSet(viewsets.GenericViewSet): |
| 1240 | + serializer_class = LiveEvaluationSerializer |
| 1241 | + |
| 1242 | + @extend_schema( |
| 1243 | + request=LiveEvaluationSerializer, |
| 1244 | + responses={ |
| 1245 | + 202: {"description": "Live evaluation done successfully"}, |
| 1246 | + 400: {"description": "Invalid request"}, |
| 1247 | + 500: {"description": "Internal server error"}, |
| 1248 | + }, |
| 1249 | + ) |
| 1250 | + @action(detail=False, methods=["post"]) |
| 1251 | + def evaluate(self, request): |
| 1252 | + serializer = self.get_serializer(data=request.data) |
| 1253 | + if not serializer.is_valid(): |
| 1254 | + return Response( |
| 1255 | + serializer.errors, |
| 1256 | + status=status.HTTP_400_BAD_REQUEST, |
| 1257 | + ) |
| 1258 | + |
| 1259 | + purl_string = serializer.validated_data.get("purl_string") |
| 1260 | + no_threading = serializer.validated_data.get("no_threading", False) |
| 1261 | + |
| 1262 | + try: |
| 1263 | + purl = PackageURL.from_string(purl_string) if purl_string else None |
| 1264 | + if not purl: |
| 1265 | + return Response({"error": "Invalid PackageURL"}, status=status.HTTP_400_BAD_REQUEST) |
| 1266 | + except Exception as e: |
| 1267 | + return Response( |
| 1268 | + {"error": f"Invalid PackageURL: {str(e)}"}, status=status.HTTP_400_BAD_REQUEST |
| 1269 | + ) |
| 1270 | + |
| 1271 | + importers = [ |
| 1272 | + importer |
| 1273 | + for importer in LIVE_IMPORTERS_REGISTRY.values() |
| 1274 | + if hasattr(importer, "supported_types") |
| 1275 | + and purl.type in getattr(importer, "supported_types", []) |
| 1276 | + ] |
| 1277 | + |
| 1278 | + if not importers: |
| 1279 | + return Response( |
| 1280 | + {"error": f"No live importers found for purl type '{purl.type}'"}, |
| 1281 | + status=status.HTTP_400_BAD_REQUEST, |
| 1282 | + ) |
| 1283 | + |
| 1284 | + results = [] |
| 1285 | + |
| 1286 | + def run_importer(importer): |
| 1287 | + importer_name = getattr(importer, "pipeline_id", importer.__name__) |
| 1288 | + response_data = {"importer": importer_name, "purl": purl_string, "steps_completed": []} |
| 1289 | + try: |
| 1290 | + pipeline_instance = importer(purl=purl) |
| 1291 | + status_code, error = pipeline_instance.execute() |
| 1292 | + if status_code != 0: |
| 1293 | + response_data["error"] = f"Importer {importer_name} failed: {error}" |
| 1294 | + else: |
| 1295 | + response_data["steps_completed"].append("import") |
| 1296 | + except Exception as e: |
| 1297 | + response_data["error"] = f"Error running importer {importer_name}: {str(e)}" |
| 1298 | + return response_data |
| 1299 | + |
| 1300 | + if not no_threading and len(importers) > 1: |
| 1301 | + with ThreadPoolExecutor(max_workers=len(importers)) as executor: |
| 1302 | + future_to_importer = { |
| 1303 | + executor.submit(run_importer, importer): importer for importer in importers |
| 1304 | + } |
| 1305 | + for future in as_completed(future_to_importer): |
| 1306 | + results.append(future.result()) |
| 1307 | + else: |
| 1308 | + for importer in importers: |
| 1309 | + results.append(run_importer(importer)) |
| 1310 | + |
| 1311 | + return Response(results, status=status.HTTP_202_ACCEPTED) |
0 commit comments