Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions cycode/cli/apps/scan/code_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,28 @@ def scan_documents(
print_local_scan_results(ctx, local_scan_results, errors)


def _perform_scan_v2_async(
cycode_client: 'ScanClient',
zipped_documents: 'InMemoryZip',
scan_type: str,
scan_parameters: dict,
is_git_diff: bool,
is_commit_range: bool,
) -> ZippedFileScanResult:
upload_link = cycode_client.get_upload_link(scan_type)
logger.debug('Got upload link, %s', {'upload_id': upload_link.upload_id})

cycode_client.upload_to_presigned_post(upload_link.url, upload_link.fields, zipped_documents)
logger.debug('Uploaded zip to presigned URL')

scan_async_result = cycode_client.scan_repository_from_upload_id(
scan_type, upload_link.upload_id, scan_parameters, is_git_diff, is_commit_range
)
logger.debug('V2 scan request triggered, %s', {'scan_id': scan_async_result.scan_id})

return poll_scan_results(cycode_client, scan_async_result.scan_id, scan_type, scan_parameters)


def _perform_scan_async(
cycode_client: 'ScanClient',
zipped_documents: 'InMemoryZip',
Expand Down Expand Up @@ -262,6 +284,11 @@ def _perform_scan(
# it does not support commit range scans; should_use_sync_flow handles it
return _perform_scan_sync(cycode_client, zipped_documents, scan_type, scan_parameters, is_git_diff)

if scan_type == consts.SAST_SCAN_TYPE:
return _perform_scan_v2_async(
cycode_client, zipped_documents, scan_type, scan_parameters, is_git_diff, is_commit_range
)

return _perform_scan_async(cycode_client, zipped_documents, scan_type, scan_parameters, is_commit_range)


Expand Down
20 changes: 20 additions & 0 deletions cycode/cyclient/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,26 @@ def build_dto(self, data: dict[str, Any], **_) -> 'ScanResult':
return ScanResult(**data)


@dataclass
class UploadLinkResponse:
upload_id: str
url: str
fields: dict[str, str]


class UploadLinkResponseSchema(Schema):
class Meta:
unknown = EXCLUDE

upload_id = fields.String()
url = fields.String()
fields = fields.Dict(keys=fields.String(), values=fields.String())

@post_load
def build_dto(self, data: dict[str, Any], **_) -> 'UploadLinkResponse':
return UploadLinkResponse(**data)


class ScanInitializationResponse(Schema):
def __init__(self, scan_id: Optional[str] = None, err: Optional[str] = None) -> None:
super().__init__()
Expand Down
39 changes: 39 additions & 0 deletions cycode/cyclient/scan_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import TYPE_CHECKING, Optional, Union
from uuid import UUID

import requests
from requests import Response

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

self._SCAN_SERVICE_CLI_CONTROLLER_PATH = 'api/v1/cli-scan'
self._SCAN_SERVICE_V2_CLI_CONTROLLER_PATH = 'api/v2/cli-scan'
self._DETECTIONS_SERVICE_CLI_CONTROLLER_PATH = 'api/v1/detections/cli'
self._POLICIES_SERVICE_CONTROLLER_PATH_V3 = 'api/v3/policies'

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

def get_scan_service_v2_url_path(self, scan_type: str) -> str:
service_path = self.scan_config.get_service_name(scan_type)
return f'{service_path}/{self._SCAN_SERVICE_V2_CLI_CONTROLLER_PATH}'

def get_zipped_file_scan_async_url_path(self, scan_type: str, should_use_sync_flow: bool = False) -> str:
async_scan_type = self.scan_config.get_async_scan_type(scan_type)
async_entity_type = self.scan_config.get_async_entity_type(scan_type)
Expand Down Expand Up @@ -123,6 +129,39 @@ def zipped_file_scan_async(
)
return models.ScanInitializationResponseSchema().load(response.json())

def get_upload_link(self, scan_type: str) -> models.UploadLinkResponse:
async_scan_type = self.scan_config.get_async_scan_type(scan_type)
url_path = f'{self.get_scan_service_v2_url_path(scan_type)}/{async_scan_type}/upload-link'
response = self.scan_cycode_client.get(url_path=url_path, hide_response_content_log=self._hide_response_log)
return models.UploadLinkResponseSchema().load(response.json())

def upload_to_presigned_post(self, url: str, fields: dict[str, str], zip_file: 'InMemoryZip') -> None:
multipart = {key: (None, value) for key, value in fields.items()}
multipart['file'] = (None, zip_file.read())
response = requests.post(url, files=multipart, timeout=self.scan_cycode_client.timeout)
response.raise_for_status()

def scan_repository_from_upload_id(
self,
scan_type: str,
upload_id: str,
scan_parameters: dict,
is_git_diff: bool = False,
is_commit_range: bool = False,
) -> models.ScanInitializationResponse:
async_scan_type = self.scan_config.get_async_scan_type(scan_type)
url_path = f'{self.get_scan_service_v2_url_path(scan_type)}/{async_scan_type}/repository'
response = self.scan_cycode_client.post(
url_path=url_path,
data={
'upload_id': upload_id,
'is_git_diff': is_git_diff,
'is_commit_range': is_commit_range,
'scan_parameters': json.dumps(scan_parameters),
},
)
return models.ScanInitializationResponseSchema().load(response.json())

def commit_range_scan_async(
self,
from_commit_zip_file: InMemoryZip,
Expand Down