Skip to content

Commit cdf5716

Browse files
authored
CM-48074 - Return report option with new name --cycode-report (#306)
1 parent 2b821af commit cdf5716

File tree

5 files changed

+51
-11
lines changed

5 files changed

+51
-11
lines changed

README.md

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ This guide walks you through both installation and usage.
2121
1. [Options](#options)
2222
1. [Severity Threshold](#severity-option)
2323
2. [Monitor](#monitor-option)
24-
3. [Package Vulnerabilities](#package-vulnerabilities-option)
25-
4. [License Compliance](#license-compliance-option)
26-
5. [Lock Restore](#lock-restore-option)
24+
3. [Cycode Report](#cycode-report-option)
25+
4. [Package Vulnerabilities](#package-vulnerabilities-option)
26+
5. [License Compliance](#license-compliance-option)
27+
6. [Lock Restore](#lock-restore-option)
2728
2. [Repository Scan](#repository-scan)
2829
1. [Branch Option](#branch-option)
2930
3. [Path Scan](#path-scan)
@@ -300,6 +301,7 @@ The Cycode CLI application offers several types of scans so that you can choose
300301
| `--severity-threshold [INFO\|LOW\|MEDIUM\|HIGH\|CRITICAL]` | Show only violations at the specified level or higher. |
301302
| `--sca-scan` | Specify the SCA scan you wish to execute (`package-vulnerabilities`/`license-compliance`). The default is both. |
302303
| `--monitor` | When specified, the scan results will be recorded in the knowledge graph. Please note that when working in `monitor` mode, the knowledge graph will not be updated as a result of SCM events (Push, Repo creation). (Supported for SCA scan type only). |
304+
| `--cycode-report` | When specified, displays a link to the scan report in the Cycode platform in the console output. |
303305
| `--no-restore` | When specified, Cycode will not run restore command. Will scan direct dependencies ONLY! |
304306
| `--gradle-all-sub-projects` | When specified, Cycode will run gradle restore command for all sub projects. Should run from root project directory ONLY! |
305307
| `--help` | Show options for given command. |
@@ -337,6 +339,25 @@ When using this option, the scan results from this scan will appear in the knowl
337339
> [!WARNING]
338340
> You must be an `owner` or an `admin` in Cycode to view the knowledge graph page.
339341
342+
#### Cycode Report Option
343+
344+
For every scan performed using the Cycode CLI, a report is automatically generated and its results are sent to Cycode. These results are tied to the relevant policies (e.g., [SCA policies](https://docs.cycode.com/docs/sca-policies) for Repository scans) within the Cycode platform.
345+
346+
To have the direct URL to this Cycode report printed in your CLI output after the scan completes, add the argument `--cycode-report` to your scan command.
347+
348+
`cycode scan --cycode-report repository ~/home/git/codebase`
349+
350+
All scan results from the CLI will appear in the CLI Logs section of Cycode. If you included the `--cycode-report` flag in your command, a direct link to the specific report will be displayed in your terminal following the scan results.
351+
352+
> [!WARNING]
353+
> You must be an `owner` or an `admin` in Cycode to view this page.
354+
355+
![cli-report](https://raw.githubusercontent.com/cycodehq/cycode-cli/main/images/sca_report_url.png)
356+
357+
The report page will look something like below:
358+
359+
![](https://raw.githubusercontent.com/cycodehq/cycode-cli/main/images/scan_details.png)
360+
340361
#### Package Vulnerabilities Option
341362
342363
> [!NOTE]

cycode/cli/apps/scan/code_scanner.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ def scan_documents(
323323
scan_batch_thread_func, scan_type, documents_to_scan, progress_bar=progress_bar
324324
)
325325

326-
aggregation_report_url = _try_get_aggregation_report_url(scan_parameters, ctx.obj['client'], scan_type)
326+
aggregation_report_url = _try_get_aggregation_report_url_if_needed(scan_parameters, ctx.obj['client'], scan_type)
327327
_set_aggregation_report_url(ctx, aggregation_report_url)
328328

329329
progress_bar.set_section_length(ScanProgressBarSection.GENERATE_REPORT, 1)
@@ -641,6 +641,7 @@ def parse_pre_receive_input() -> str:
641641
def _get_default_scan_parameters(ctx: typer.Context) -> dict:
642642
return {
643643
'monitor': ctx.obj.get('monitor'),
644+
'report': ctx.obj.get('report'),
644645
'package_vulnerabilities': ctx.obj.get('package-vulnerabilities'),
645646
'license_compliance': ctx.obj.get('license-compliance'),
646647
'command_type': ctx.info_name.replace('-', '_'), # save backward compatibility
@@ -956,7 +957,7 @@ def _get_scan_result(
956957
did_detect=True,
957958
detections_per_file=_map_detections_per_file_and_commit_id(scan_type, scan_raw_detections),
958959
scan_id=scan_id,
959-
report_url=_try_get_aggregation_report_url(scan_parameters, cycode_client, scan_type),
960+
report_url=_try_get_aggregation_report_url_if_needed(scan_parameters, cycode_client, scan_type),
960961
)
961962

962963

@@ -972,9 +973,12 @@ def _set_aggregation_report_url(ctx: typer.Context, aggregation_report_url: Opti
972973
ctx.obj['aggregation_report_url'] = aggregation_report_url
973974

974975

975-
def _try_get_aggregation_report_url(
976+
def _try_get_aggregation_report_url_if_needed(
976977
scan_parameters: dict, cycode_client: 'ScanClient', scan_type: str
977978
) -> Optional[str]:
979+
if not scan_parameters.get('report', False):
980+
return None
981+
978982
aggregation_id = scan_parameters.get('aggregation_id')
979983
if aggregation_id is None:
980984
return None

cycode/cli/apps/scan/scan_command.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ def scan_command(
4545
'--sync', help='Run scan synchronously (INTERNAL FOR IDEs).', show_default='asynchronously', hidden=True
4646
),
4747
] = False,
48+
report: Annotated[
49+
bool,
50+
typer.Option(
51+
'--cycode-report',
52+
help='When specified, displays a link to the scan report in the Cycode platform in the console output.',
53+
),
54+
] = False,
4855
show_secret: Annotated[
4956
bool, typer.Option('--show-secret', help='Show Secrets in plain text.', rich_help_panel=_SECRET_RICH_HELP_PANEL)
5057
] = False,
@@ -136,6 +143,7 @@ def scan_command(
136143
ctx.obj['sync'] = sync
137144
ctx.obj['severity_threshold'] = severity_threshold
138145
ctx.obj['monitor'] = monitor
146+
ctx.obj['report'] = report
139147

140148
if export_type and export_file:
141149
console_printer = ctx.obj['console_printer']

cycode/cyclient/scan_client.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
from copy import deepcopy
23
from typing import TYPE_CHECKING, Union
34
from uuid import UUID
45

@@ -73,6 +74,11 @@ def zipped_file_scan_sync(
7374
is_git_diff: bool = False,
7475
) -> models.ScanResultsSyncFlow:
7576
files = {'file': ('multiple_files_scan.zip', zip_file.read())}
77+
78+
scan_parameters = deepcopy(scan_parameters) # avoid mutating the original dict
79+
if 'report' in scan_parameters:
80+
del scan_parameters['report'] # BE raises validation error instead of ignoring it
81+
7682
response = self.scan_cycode_client.post(
7783
url_path=self.get_zipped_file_scan_sync_url_path(scan_type),
7884
data={

tests/test_code_scanner.py

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

77
from cycode.cli import consts
88
from cycode.cli.apps.scan.code_scanner import (
9-
_try_get_aggregation_report_url,
9+
_try_get_aggregation_report_url_if_needed,
1010
)
1111
from cycode.cli.cli_types import ScanTypeOption
1212
from cycode.cli.files_collector.excluder import _is_relevant_file_to_scan
@@ -29,15 +29,16 @@ def test_try_get_aggregation_report_url_if_no_report_command_needed_return_none(
2929
) -> None:
3030
aggregation_id = uuid4().hex
3131
scan_parameter = {'aggregation_id': aggregation_id}
32-
result = _try_get_aggregation_report_url(scan_parameter, scan_client, scan_type)
32+
result = _try_get_aggregation_report_url_if_needed(scan_parameter, scan_client, scan_type)
3333
assert result is None
3434

3535

3636
@pytest.mark.parametrize('scan_type', list(ScanTypeOption))
3737
def test_try_get_aggregation_report_url_if_no_aggregation_id_needed_return_none(
3838
scan_type: ScanTypeOption, scan_client: ScanClient
3939
) -> None:
40-
result = _try_get_aggregation_report_url({}, scan_client, scan_type)
40+
scan_parameter = {'report': True}
41+
result = _try_get_aggregation_report_url_if_needed(scan_parameter, scan_client, scan_type)
4142
assert result is None
4243

4344

@@ -47,12 +48,12 @@ def test_try_get_aggregation_report_url_if_needed_return_result(
4748
scan_type: ScanTypeOption, scan_client: ScanClient, api_token_response: responses.Response
4849
) -> None:
4950
aggregation_id = uuid4()
50-
scan_parameter = {'aggregation_id': aggregation_id}
51+
scan_parameter = {'report': True, 'aggregation_id': aggregation_id}
5152
url = get_scan_aggregation_report_url(aggregation_id, scan_client, scan_type)
5253
responses.add(api_token_response) # mock token based client
5354
responses.add(get_scan_aggregation_report_url_response(url, aggregation_id))
5455

5556
scan_aggregation_report_url_response = scan_client.get_scan_aggregation_report_url(str(aggregation_id), scan_type)
5657

57-
result = _try_get_aggregation_report_url(scan_parameter, scan_client, scan_type)
58+
result = _try_get_aggregation_report_url_if_needed(scan_parameter, scan_client, scan_type)
5859
assert result == scan_aggregation_report_url_response.report_url

0 commit comments

Comments
 (0)