Skip to content

Commit b0761cc

Browse files
authored
Merge pull request #511 from nexB/504-scan-queue-api-update
504 scan queue api update
2 parents c9d15ea + 499e357 commit b0761cc

File tree

10 files changed

+194
-155
lines changed

10 files changed

+194
-155
lines changed

CHANGELOG.rst

+8-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@ Next Release
1111
removed.
1212
- The `/api/collect/` and `/api/collect/index_packages/` API endpoints have been
1313
updated such that Package scan and processing requests made with purls with
14-
versions are processed ahead of those made with versionless purls. https://github.com/nexB/purldb/issues/502
14+
versions are processed ahead of those made with versionless purls.
15+
https://github.com/nexB/purldb/issues/502
16+
- The `/api/scan_queue/` endpoint has been updated.
17+
`/api/scan_queue/get_next_download_url/` now returns a `webhook_url`, where
18+
the purldb scan worker will submit results. This is a URL to the
19+
`/api/scan_queue/index_package_scan/` endpoint.
20+
`/api/scan_queue/update_status/` is now an action on a ScannableURI.
21+
https://github.com/nexB/purldb/issues/504
1522

1623

1724
v5.0.0

minecode/api.py

+78-71
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,19 @@
77
# See https://aboutcode.org for more information about nexB OSS projects.
88
#
99

10+
import json
11+
12+
from django.contrib.auth import get_user_model
13+
from django.core import signing
1014
from django.db import transaction
15+
from django.http import Http404
16+
from django.shortcuts import get_object_or_404
1117
from django.utils import timezone
18+
from django.views.decorators.csrf import csrf_exempt
19+
1220
from packageurl import PackageURL
1321
from rest_framework import serializers, status, viewsets
14-
from rest_framework.decorators import action
22+
from rest_framework.decorators import action, api_view
1523
from rest_framework.permissions import IsAdminUser
1624
from rest_framework.response import Response
1725

@@ -21,8 +29,8 @@
2129
from minecode import priority_router
2230
from minecode.models import PriorityResourceURI, ResourceURI, ScannableURI
2331
from minecode.permissions import IsScanQueueWorkerAPIUser
24-
from minecode.utils import validate_uuid
2532
from minecode.utils import get_temp_file
33+
from minecode.utils import get_webhook_url
2634

2735

2836
class ResourceURISerializer(serializers.ModelSerializer):
@@ -98,6 +106,7 @@ class ScannableURIViewSet(viewsets.ModelViewSet):
98106
queryset = ScannableURI.objects.all()
99107
serializer_class = ScannableURISerializer
100108
permission_classes = [IsScanQueueWorkerAPIUser|IsAdminUser]
109+
lookup_field = 'uuid'
101110

102111
@action(detail=False, methods=['get'])
103112
def get_next_download_url(self, request, *args, **kwargs):
@@ -107,10 +116,13 @@ def get_next_download_url(self, request, *args, **kwargs):
107116
with transaction.atomic():
108117
scannable_uri = ScannableURI.objects.get_next_scannable()
109118
if scannable_uri:
119+
user = self.request.user
120+
webhook_url = get_webhook_url('index_package_scan', user.id)
110121
response = {
111122
'scannable_uri_uuid': scannable_uri.uuid,
112123
'download_url': scannable_uri.uri,
113124
'pipelines': scannable_uri.pipelines,
125+
'webhook_url': webhook_url,
114126
}
115127
scannable_uri.scan_status = ScannableURI.SCAN_SUBMITTED
116128
scannable_uri.scan_date = timezone.now()
@@ -120,52 +132,28 @@ def get_next_download_url(self, request, *args, **kwargs):
120132
'scannable_uri_uuid': '',
121133
'download_url': '',
122134
'pipelines': [],
135+
'webhook_url': '',
123136
}
124137
return Response(response)
125138

126-
@action(detail=False, methods=['post'])
139+
@action(detail=True, methods=['post'])
127140
def update_status(self, request, *args, **kwargs):
128141
"""
129-
Update the status of a ScannableURI with UUID of `scannable_uri_uuid`
130-
with `scan_status`
142+
Update the status of a ScannableURI with `scan_status`
131143
132144
If `scan_status` is 'failed', then a `scan_log` string is expected and
133145
should contain the error messages for that scan.
134-
135-
If `scan_status` is 'scanned', then a `scan_results_file`,
136-
`scan_summary_file`, and `project_extra_data` mapping are expected.
137-
`scan_results_file`, `scan_summary_file`, and `project_extra_data` are
138-
then used to update Package data and its Resources.
139146
"""
140-
scannable_uri_uuid = request.data.get('scannable_uri_uuid')
141147
scan_status = request.data.get('scan_status')
142-
if not scannable_uri_uuid:
143-
response = {
144-
'error': 'missing scannable_uri_uuid'
145-
}
146-
return Response(response, status=status.HTTP_400_BAD_REQUEST)
147-
148148
if not scan_status:
149149
response = {
150150
'error': 'missing scan_status'
151151
}
152152
return Response(response, status=status.HTTP_400_BAD_REQUEST)
153153

154-
if not validate_uuid(scannable_uri_uuid):
155-
response = {
156-
'error': f'invalid scannable_uri_uuid: {scannable_uri_uuid}'
157-
}
158-
return Response(response, status=status.HTTP_400_BAD_REQUEST)
159-
160-
scannable_uri = ScannableURI.objects.get(uuid=scannable_uri_uuid)
154+
scannable_uri = self.get_object()
155+
scannable_uri_uuid = scannable_uri.uuid
161156
scannable_uri_status = ScannableURI.SCAN_STATUSES_BY_CODE.get(scannable_uri.scan_status)
162-
scan_status_code = ScannableURI.SCAN_STATUS_CODES_BY_SCAN_STATUS.get(scan_status)
163-
164-
if not scan_status_code:
165-
msg = {
166-
'error': f'invalid scan_status: {scan_status}'
167-
}
168-
return Response(msg, status=status.HTTP_400_BAD_REQUEST)
169157

170158
if scannable_uri.scan_status in [
171159
ScannableURI.SCAN_INDEXED,
@@ -192,46 +180,65 @@ def update_status(self, request, *args, **kwargs):
192180
scannable_uri.scan_status = ScannableURI.SCAN_FAILED
193181
scannable_uri.wip_date = None
194182
scannable_uri.save()
195-
msg = {
183+
response = {
196184
'status': f'updated scannable_uri {scannable_uri_uuid} scan_status to {scan_status}'
197185
}
186+
return Response(response)
198187

199-
elif scan_status == 'scanned':
200-
scan_results_file = request.data.get('scan_results_file')
201-
scan_summary_file = request.data.get('scan_summary_file')
202-
project_extra_data = request.data.get('project_extra_data')
203-
204-
# Save results to temporary files
205-
scan_results_location = get_temp_file(
206-
file_name='scan_results',
207-
extension='.json'
208-
)
209-
scan_summary_location = get_temp_file(
210-
file_name='scan_summary',
211-
extension='.json'
212-
)
213-
with open(scan_results_location, 'wb') as f:
214-
f.write(scan_results_file.read())
215-
with open(scan_summary_location, 'wb') as f:
216-
f.write(scan_summary_file.read())
217-
218-
scannable_uri.process_scan_results(
219-
scan_results_location=scan_results_location,
220-
scan_summary_location=scan_summary_location,
221-
project_extra_data=project_extra_data
222-
)
223-
msg = {
224-
'status': f'scan results for scannable_uri {scannable_uri_uuid} '
225-
'have been queued for indexing'
226-
}
227-
228-
return Response(msg)
229-
230-
@action(detail=False, methods=['get'])
231-
def statistics(self, request, *args, **kwargs):
232-
"""
233-
Return a scan queue statistics.
234-
"""
235-
response = ScannableURI.objects.statistics()
236-
return Response(response)
237-
188+
response = {
189+
'error': f'invalid scan_status: {scan_status}'
190+
}
191+
return Response(response, status=status.HTTP_400_BAD_REQUEST)
192+
193+
194+
@api_view(['POST'])
195+
@csrf_exempt
196+
def index_package_scan(request, key):
197+
"""
198+
Given a `request` to the `/api/scan_queue/index_package_scan/<key>/`
199+
endpoint, where `key` is the id of the purldb scan queue worker that has
200+
been encoded as a secret, save the package scan results and summary to files
201+
and create a new rq worker task to index the scan results and summary.
202+
"""
203+
try:
204+
json_data = json.loads(request.body.decode("utf-8"))
205+
except json.JSONDecodeError:
206+
raise Http404
207+
208+
user_id = signing.loads(key)
209+
User = get_user_model()
210+
user = get_object_or_404(User, id=user_id)
211+
212+
results = json_data.get('results')
213+
summary = json_data.get('summary')
214+
project_data = json_data.get('project')
215+
extra_data = project_data.get('extra_data')
216+
scannable_uri_uuid = extra_data.get('scannable_uri_uuid')
217+
218+
# Save results to temporary files
219+
scan_results_location = get_temp_file(
220+
file_name='scan_results',
221+
extension='.json'
222+
)
223+
scan_summary_location = get_temp_file(
224+
file_name='scan_summary',
225+
extension='.json'
226+
)
227+
228+
with open(scan_results_location, 'w') as f:
229+
json.dump(results, f)
230+
231+
with open(scan_summary_location, 'w') as f:
232+
json.dump(summary, f)
233+
234+
scannable_uri = get_object_or_404(ScannableURI, uuid=scannable_uri_uuid)
235+
scannable_uri.process_scan_results(
236+
scan_results_location=scan_results_location,
237+
scan_summary_location=scan_summary_location,
238+
project_extra_data=extra_data
239+
)
240+
msg = {
241+
'status': f'scan results for scannable_uri {scannable_uri.uuid} '
242+
'have been queued for indexing'
243+
}
244+
return Response(msg)

minecode/models.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
# See https://aboutcode.org for more information about nexB OSS projects.
88
#
99

10-
import uuid
1110
from datetime import timedelta
1211
import logging
1312
import sys
13+
import uuid
1414

1515
from django.conf import settings
1616
from django.db import models
1717
from django.utils import timezone
18+
1819
import django_rq
1920

2021
from minecode import map_router

minecode/tasks.py

-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ def process_scan_results(
3434
scan_data = json.load(f)
3535
with open(scan_summary_location) as f:
3636
summary_data = json.load(f)
37-
project_extra_data = json.loads(project_extra_data)
3837

3938
try:
4039
scannable_uri = ScannableURI.objects.get(uuid=scannable_uri_uuid)

0 commit comments

Comments
 (0)