Skip to content

Commit dff8b7a

Browse files
CM-60184-Commit range scans
1 parent 0076612 commit dff8b7a

File tree

7 files changed

+128
-18
lines changed

7 files changed

+128
-18
lines changed

cycode/cli/apps/scan/code_scanner.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
generate_unique_scan_id,
3030
is_cycodeignore_allowed_by_scan_config,
3131
set_issue_detected_by_scan_results,
32+
should_use_presigned_upload,
3233
)
3334
from cycode.cyclient.models import ZippedFileScanResult
3435
from cycode.logger import get_logger
@@ -106,7 +107,10 @@ def _should_use_sync_flow(command_scan_type: str, scan_type: str, sync_option: b
106107

107108

108109
def _get_scan_documents_thread_func(
109-
ctx: typer.Context, is_git_diff: bool, is_commit_range: bool, scan_parameters: dict
110+
ctx: typer.Context,
111+
is_git_diff: bool,
112+
is_commit_range: bool,
113+
scan_parameters: dict,
110114
) -> Callable[[list[Document]], tuple[str, CliError, LocalScanResult]]:
111115
cycode_client = ctx.obj['client']
112116
scan_type = ctx.obj['scan_type']
@@ -202,11 +206,38 @@ def scan_documents(
202206
)
203207
return
204208

205-
scan_batch_thread_func = _get_scan_documents_thread_func(ctx, is_git_diff, is_commit_range, scan_parameters)
206-
errors, local_scan_results = run_parallel_batched_scan(
207-
scan_batch_thread_func, scan_type, documents_to_scan, progress_bar=progress_bar
209+
scan_batch_thread_func = _get_scan_documents_thread_func(
210+
ctx, is_git_diff, is_commit_range, scan_parameters
208211
)
209212

213+
if should_use_presigned_upload(scan_type):
214+
try:
215+
# Try to zip all documents as a single batch; ZipTooLargeError raised if it exceeds the scan type's limit
216+
zip_documents(scan_type, documents_to_scan)
217+
# It fits: skip batching and upload everything as one ZIP
218+
errors, local_scan_results = run_parallel_batched_scan(
219+
scan_batch_thread_func,
220+
scan_type,
221+
documents_to_scan,
222+
progress_bar=progress_bar,
223+
skip_batching=True,
224+
)
225+
except custom_exceptions.ZipTooLargeError:
226+
printer.print_warning(
227+
'The repository is too large to upload as a single file. '
228+
'Falling back to batched scanning. This may result in multiple scan results.'
229+
)
230+
errors, local_scan_results = run_parallel_batched_scan(
231+
scan_batch_thread_func,
232+
scan_type,
233+
documents_to_scan,
234+
progress_bar=progress_bar,
235+
)
236+
else:
237+
errors, local_scan_results = run_parallel_batched_scan(
238+
scan_batch_thread_func, scan_type, documents_to_scan, progress_bar=progress_bar
239+
)
240+
210241
try_set_aggregation_report_url_if_needed(ctx, scan_parameters, ctx.obj['client'], scan_type)
211242

212243
progress_bar.set_section_length(ScanProgressBarSection.GENERATE_REPORT, 1)

cycode/cli/apps/scan/commit_range_scanner.py

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,38 @@ def _perform_commit_range_scan_async(
8686
return poll_scan_results(cycode_client, scan_async_result.scan_id, scan_type, scan_parameters, timeout)
8787

8888

89+
def _perform_commit_range_scan_v4_async(
90+
cycode_client: 'ScanClient',
91+
from_commit_zipped_documents: 'InMemoryZip',
92+
to_commit_zipped_documents: 'InMemoryZip',
93+
scan_type: str,
94+
scan_parameters: dict,
95+
timeout: Optional[int] = None,
96+
) -> ZippedFileScanResult:
97+
from_upload_link = cycode_client.get_upload_link(scan_type)
98+
logger.debug('Got from-commit upload link, %s', {'upload_id': from_upload_link.upload_id})
99+
100+
cycode_client.upload_to_presigned_post(
101+
from_upload_link.url, from_upload_link.presigned_post_fields, from_commit_zipped_documents
102+
)
103+
logger.debug('Uploaded from-commit zip')
104+
105+
to_upload_link = cycode_client.get_upload_link(scan_type)
106+
logger.debug('Got to-commit upload link, %s', {'upload_id': to_upload_link.upload_id})
107+
108+
cycode_client.upload_to_presigned_post(
109+
to_upload_link.url, to_upload_link.presigned_post_fields, to_commit_zipped_documents
110+
)
111+
logger.debug('Uploaded to-commit zip')
112+
113+
scan_async_result = cycode_client.commit_range_scan_from_upload_ids(
114+
scan_type, from_upload_link.upload_id, to_upload_link.upload_id, scan_parameters
115+
)
116+
logger.debug('V4 commit range scan request triggered, %s', {'scan_id': scan_async_result.scan_id})
117+
118+
return poll_scan_results(cycode_client, scan_async_result.scan_id, scan_type, scan_parameters, timeout)
119+
120+
89121
def _scan_commit_range_documents(
90122
ctx: typer.Context,
91123
from_documents_to_scan: list[Document],
@@ -118,14 +150,24 @@ def _scan_commit_range_documents(
118150
# for SAST it is files with diff between from_commit and to_commit
119151
to_commit_zipped_documents = zip_documents(scan_type, to_documents_to_scan)
120152

121-
scan_result = _perform_commit_range_scan_async(
122-
cycode_client,
123-
from_commit_zipped_documents,
124-
to_commit_zipped_documents,
125-
scan_type,
126-
scan_parameters,
127-
timeout,
128-
)
153+
if scan_type == consts.SAST_SCAN_TYPE:
154+
scan_result = _perform_commit_range_scan_v4_async(
155+
cycode_client,
156+
from_commit_zipped_documents,
157+
to_commit_zipped_documents,
158+
scan_type,
159+
scan_parameters,
160+
timeout,
161+
)
162+
else:
163+
scan_result = _perform_commit_range_scan_async(
164+
cycode_client,
165+
from_commit_zipped_documents,
166+
to_commit_zipped_documents,
167+
scan_type,
168+
scan_parameters,
169+
timeout,
170+
)
129171
enrich_scan_result_with_data_from_detection_rules(cycode_client, scan_result)
130172

131173
progress_bar.update(ScanProgressBarSection.SCAN)

cycode/cli/consts.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@
166166
COMMIT_HISTORY_COMMAND_SCAN_TYPE_OLD,
167167
]
168168

169-
DEFAULT_CYCODE_DOMAIN = 'cycode.com'
169+
DEFAULT_CYCODE_DOMAIN = 'cycode.xyz'
170170
DEFAULT_CYCODE_API_URL = f'https://api.{DEFAULT_CYCODE_DOMAIN}'
171171
DEFAULT_CYCODE_APP_URL = f'https://app.{DEFAULT_CYCODE_DOMAIN}'
172172

@@ -192,15 +192,18 @@
192192
# 5MB in bytes (in decimal)
193193
FILE_MAX_SIZE_LIMIT_IN_BYTES = 5000000
194194

195+
PRESIGNED_LINK_UPLOADED_ZIP_MAX_SIZE_LIMIT_IN_BYTES = 5 * 1024 * 1024 * 1024 # 5 GB (S3 presigned POST limit)
196+
PRESIGNED_UPLOAD_SCAN_TYPES = {SAST_SCAN_TYPE}
197+
195198
DEFAULT_ZIP_MAX_SIZE_LIMIT_IN_BYTES = 20 * 1024 * 1024
196199
ZIP_MAX_SIZE_LIMIT_IN_BYTES = {
197200
SCA_SCAN_TYPE: 200 * 1024 * 1024,
198-
SAST_SCAN_TYPE: 50 * 1024 * 1024,
201+
SAST_SCAN_TYPE: PRESIGNED_LINK_UPLOADED_ZIP_MAX_SIZE_LIMIT_IN_BYTES,
199202
}
200203

201204
# scan in batches
202205
DEFAULT_SCAN_BATCH_MAX_SIZE_IN_BYTES = 9 * 1024 * 1024
203-
SCAN_BATCH_MAX_SIZE_IN_BYTES = {SAST_SCAN_TYPE: 50 * 1024 * 1024}
206+
SCAN_BATCH_MAX_SIZE_IN_BYTES = {SAST_SCAN_TYPE: PRESIGNED_LINK_UPLOADED_ZIP_MAX_SIZE_LIMIT_IN_BYTES}
204207
SCAN_BATCH_MAX_SIZE_IN_BYTES_ENV_VAR_NAME = 'SCAN_BATCH_MAX_SIZE_IN_BYTES'
205208

206209
DEFAULT_SCAN_BATCH_MAX_FILES_COUNT = 1000

cycode/cli/files_collector/zip_documents.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ def _validate_zip_file_size(scan_type: str, zip_file_size: int) -> None:
1717
raise custom_exceptions.ZipTooLargeError(max_size_limit)
1818

1919

20-
def zip_documents(scan_type: str, documents: list[Document], zip_file: Optional[InMemoryZip] = None) -> InMemoryZip:
20+
def zip_documents(
21+
scan_type: str,
22+
documents: list[Document],
23+
zip_file: Optional[InMemoryZip] = None,
24+
) -> InMemoryZip:
2125
if zip_file is None:
2226
zip_file = InMemoryZip()
2327

cycode/cli/utils/scan_batch.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,13 @@ def run_parallel_batched_scan(
111111
scan_type: str,
112112
documents: list[Document],
113113
progress_bar: 'BaseProgressBar',
114+
skip_batching: bool = False,
114115
) -> tuple[dict[str, 'CliError'], list['LocalScanResult']]:
115116
# batching is disabled for SCA; requested by Mor
116-
batches = [documents] if scan_type == consts.SCA_SCAN_TYPE else split_documents_into_batches(scan_type, documents)
117+
if scan_type == consts.SCA_SCAN_TYPE or skip_batching:
118+
batches = [documents]
119+
else:
120+
batches = split_documents_into_batches(scan_type, documents)
117121

118122
progress_bar.set_section_length(ScanProgressBarSection.SCAN, len(batches)) # * 3
119123
# TODO(MarshalX): we should multiply the count of batches in SCAN section because each batch has 3 steps:

cycode/cli/utils/scan_utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import typer
77

8+
from cycode.cli import consts
89
from cycode.cli.cli_types import SeverityOption
910

1011
if TYPE_CHECKING:
@@ -31,6 +32,10 @@ def is_cycodeignore_allowed_by_scan_config(ctx: typer.Context) -> bool:
3132
return scan_config.is_cycode_ignore_allowed if scan_config else True
3233

3334

35+
def should_use_presigned_upload(scan_type: str) -> bool:
36+
return scan_type in consts.PRESIGNED_UPLOAD_SCAN_TYPES
37+
38+
3439
def generate_unique_scan_id() -> UUID:
3540
if 'PYTEST_TEST_UNIQUE_ID' in os.environ:
3641
return UUID(os.environ['PYTEST_TEST_UNIQUE_ID'])

cycode/cyclient/scan_client.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def scan_repository_from_upload_id(
153153
url_path = f'{self.get_scan_service_v4_url_path(scan_type)}/{async_scan_type}/repository'
154154
response = self.scan_cycode_client.post(
155155
url_path=url_path,
156-
data={
156+
body={
157157
'upload_id': upload_id,
158158
'is_git_diff': is_git_diff,
159159
'is_commit_range': is_commit_range,
@@ -200,6 +200,27 @@ def commit_range_scan_async(
200200
)
201201
return models.ScanInitializationResponseSchema().load(response.json())
202202

203+
def commit_range_scan_from_upload_ids(
204+
self,
205+
scan_type: str,
206+
from_upload_id: str,
207+
to_upload_id: str,
208+
scan_parameters: dict,
209+
is_git_diff: bool = False,
210+
) -> models.ScanInitializationResponse:
211+
async_scan_type = self.scan_config.get_async_scan_type(scan_type)
212+
url_path = f'{self.get_scan_service_v4_url_path(scan_type)}/{async_scan_type}/repository/commit-range'
213+
response = self.scan_cycode_client.post(
214+
url_path=url_path,
215+
body={
216+
'from_upload_id': from_upload_id,
217+
'to_upload_id': to_upload_id,
218+
'is_git_diff': is_git_diff,
219+
'scan_parameters': json.dumps(scan_parameters),
220+
},
221+
)
222+
return models.ScanInitializationResponseSchema().load(response.json())
223+
203224
def get_scan_details_path(self, scan_type: str, scan_id: str) -> str:
204225
return f'{self.get_scan_service_url_path(scan_type)}/{scan_id}'
205226

0 commit comments

Comments
 (0)