Skip to content

Commit dec4f9f

Browse files
CM-60184-Scans using presigned post url
1 parent 8e4450c commit dec4f9f

File tree

3 files changed

+86
-0
lines changed

3 files changed

+86
-0
lines changed

cycode/cli/apps/scan/code_scanner.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,28 @@ def scan_documents(
217217
print_local_scan_results(ctx, local_scan_results, errors)
218218

219219

220+
def _perform_scan_v2_async(
221+
cycode_client: 'ScanClient',
222+
zipped_documents: 'InMemoryZip',
223+
scan_type: str,
224+
scan_parameters: dict,
225+
is_git_diff: bool,
226+
is_commit_range: bool,
227+
) -> ZippedFileScanResult:
228+
upload_link = cycode_client.get_upload_link(scan_type)
229+
logger.debug('Got upload link, %s', {'upload_id': upload_link.upload_id})
230+
231+
cycode_client.upload_to_presigned_post(upload_link.url, upload_link.fields, zipped_documents)
232+
logger.debug('Uploaded zip to presigned URL')
233+
234+
scan_async_result = cycode_client.scan_repository_from_upload_id(
235+
scan_type, upload_link.upload_id, scan_parameters, is_git_diff, is_commit_range
236+
)
237+
logger.debug('V2 scan request triggered, %s', {'scan_id': scan_async_result.scan_id})
238+
239+
return poll_scan_results(cycode_client, scan_async_result.scan_id, scan_type, scan_parameters)
240+
241+
220242
def _perform_scan_async(
221243
cycode_client: 'ScanClient',
222244
zipped_documents: 'InMemoryZip',
@@ -262,6 +284,11 @@ def _perform_scan(
262284
# it does not support commit range scans; should_use_sync_flow handles it
263285
return _perform_scan_sync(cycode_client, zipped_documents, scan_type, scan_parameters, is_git_diff)
264286

287+
if scan_type == consts.SAST_SCAN_TYPE:
288+
return _perform_scan_v2_async(
289+
cycode_client, zipped_documents, scan_type, scan_parameters, is_git_diff, is_commit_range
290+
)
291+
265292
return _perform_scan_async(cycode_client, zipped_documents, scan_type, scan_parameters, is_commit_range)
266293

267294

cycode/cyclient/models.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,26 @@ def build_dto(self, data: dict[str, Any], **_) -> 'ScanResult':
114114
return ScanResult(**data)
115115

116116

117+
@dataclass
118+
class UploadLinkResponse:
119+
upload_id: str
120+
url: str
121+
fields: dict[str, str]
122+
123+
124+
class UploadLinkResponseSchema(Schema):
125+
class Meta:
126+
unknown = EXCLUDE
127+
128+
upload_id = fields.String()
129+
url = fields.String()
130+
fields = fields.Dict(keys=fields.String(), values=fields.String())
131+
132+
@post_load
133+
def build_dto(self, data: dict[str, Any], **_) -> 'UploadLinkResponse':
134+
return UploadLinkResponse(**data)
135+
136+
117137
class ScanInitializationResponse(Schema):
118138
def __init__(self, scan_id: Optional[str] = None, err: Optional[str] = None) -> None:
119139
super().__init__()

cycode/cyclient/scan_client.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import TYPE_CHECKING, Optional, Union
44
from uuid import UUID
55

6+
import requests
67
from requests import Response
78

89
from cycode.cli import consts
@@ -25,6 +26,7 @@ def __init__(
2526
self.scan_config = scan_config
2627

2728
self._SCAN_SERVICE_CLI_CONTROLLER_PATH = 'api/v1/cli-scan'
29+
self._SCAN_SERVICE_V2_CLI_CONTROLLER_PATH = 'api/v2/cli-scan'
2830
self._DETECTIONS_SERVICE_CLI_CONTROLLER_PATH = 'api/v1/detections/cli'
2931
self._POLICIES_SERVICE_CONTROLLER_PATH_V3 = 'api/v3/policies'
3032

@@ -56,6 +58,10 @@ def get_scan_aggregation_report_url(self, aggregation_id: str, scan_type: str) -
5658
)
5759
return models.ScanReportUrlResponseSchema().build_dto(response.json())
5860

61+
def get_scan_service_v2_url_path(self, scan_type: str) -> str:
62+
service_path = self.scan_config.get_service_name(scan_type)
63+
return f'{service_path}/{self._SCAN_SERVICE_V2_CLI_CONTROLLER_PATH}'
64+
5965
def get_zipped_file_scan_async_url_path(self, scan_type: str, should_use_sync_flow: bool = False) -> str:
6066
async_scan_type = self.scan_config.get_async_scan_type(scan_type)
6167
async_entity_type = self.scan_config.get_async_entity_type(scan_type)
@@ -123,6 +129,39 @@ def zipped_file_scan_async(
123129
)
124130
return models.ScanInitializationResponseSchema().load(response.json())
125131

132+
def get_upload_link(self, scan_type: str) -> models.UploadLinkResponse:
133+
async_scan_type = self.scan_config.get_async_scan_type(scan_type)
134+
url_path = f'{self.get_scan_service_v2_url_path(scan_type)}/{async_scan_type}/upload-link'
135+
response = self.scan_cycode_client.get(url_path=url_path, hide_response_content_log=self._hide_response_log)
136+
return models.UploadLinkResponseSchema().load(response.json())
137+
138+
def upload_to_presigned_post(self, url: str, fields: dict[str, str], zip_file: 'InMemoryZip') -> None:
139+
multipart = {key: (None, value) for key, value in fields.items()}
140+
multipart['file'] = (None, zip_file.read())
141+
response = requests.post(url, files=multipart, timeout=self.scan_cycode_client.timeout)
142+
response.raise_for_status()
143+
144+
def scan_repository_from_upload_id(
145+
self,
146+
scan_type: str,
147+
upload_id: str,
148+
scan_parameters: dict,
149+
is_git_diff: bool = False,
150+
is_commit_range: bool = False,
151+
) -> models.ScanInitializationResponse:
152+
async_scan_type = self.scan_config.get_async_scan_type(scan_type)
153+
url_path = f'{self.get_scan_service_v2_url_path(scan_type)}/{async_scan_type}/repository'
154+
response = self.scan_cycode_client.post(
155+
url_path=url_path,
156+
data={
157+
'upload_id': upload_id,
158+
'is_git_diff': is_git_diff,
159+
'is_commit_range': is_commit_range,
160+
'scan_parameters': json.dumps(scan_parameters),
161+
},
162+
)
163+
return models.ScanInitializationResponseSchema().load(response.json())
164+
126165
def commit_range_scan_async(
127166
self,
128167
from_commit_zip_file: InMemoryZip,

0 commit comments

Comments
 (0)