Skip to content

Commit 5ca59e9

Browse files
authored
CM-22207 - Format auth command output to JSON (#104)
1 parent a5f1c86 commit 5ca59e9

File tree

14 files changed

+298
-202
lines changed

14 files changed

+298
-202
lines changed

.gitignore

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,41 @@
1-
__pycache__
2-
/build/
3-
/*.egg-info/
4-
/dist/
1+
.idea
2+
*.iml
3+
.env
54

6-
# coverage
5+
# Byte-compiled / optimized / DLL files
6+
__pycache__/
7+
*.py[cod]
8+
*$py.class
9+
10+
# Distribution / packaging
11+
.Python
12+
build/
13+
develop-eggs/
14+
dist/
15+
downloads/
16+
eggs/
17+
.eggs/
18+
lib/
19+
lib64/
20+
parts/
21+
sdist/
22+
var/
23+
wheels/
24+
pip-wheel-metadata/
25+
share/python-wheels/
26+
*.egg-info/
27+
.installed.cfg
28+
*.egg
29+
30+
# Unit test / coverage reports
31+
htmlcov/
32+
.tox/
33+
.nox/
734
.coverage
8-
htmlcov
9-
coverage.*
35+
.coverage.*
36+
.cache
37+
nosetests.xml
38+
coverage.xml
39+
*.cover
40+
.hypothesis/
41+
.pytest_cache/

cycode/cli/auth/auth_command.py

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import json
2-
31
import click
42
import traceback
53

4+
from cycode.cli.models import CliResult, CliErrors, CliError
5+
from cycode.cli.printers import ConsolePrinter
66
from cycode.cli.auth.auth_manager import AuthManager
77
from cycode.cli.user_settings.credentials_manager import CredentialsManager
88
from cycode.cli.exceptions.custom_exceptions import AuthProcessError, NetworkError, HttpUnauthorizedError
@@ -19,10 +19,13 @@ def authenticate(context: click.Context):
1919
return
2020

2121
try:
22-
logger.debug("starting authentication process")
22+
logger.debug('Starting authentication process')
23+
2324
auth_manager = AuthManager()
2425
auth_manager.authenticate()
25-
click.echo("Successfully logged into cycode")
26+
27+
result = CliResult(success=True, message='Successfully logged into cycode')
28+
ConsolePrinter(context).print_result(result)
2629
except Exception as e:
2730
_handle_exception(context, e)
2831

@@ -31,49 +34,45 @@ def authenticate(context: click.Context):
3134
@click.pass_context
3235
def authorization_check(context: click.Context):
3336
""" Check your machine associating CLI with your cycode account """
34-
passed_auth_check_args = {'context': context, 'content': {
35-
'success': True,
36-
'message': 'You are authorized'
37-
}, 'color': 'green'}
38-
failed_auth_check_args = {'context': context, 'content': {
39-
'success': False,
40-
'message': 'You are not authorized'
41-
}, 'color': 'red'}
37+
printer = ConsolePrinter(context)
38+
39+
passed_auth_check_res = CliResult(success=True, message='You are authorized')
40+
failed_auth_check_res = CliResult(success=False, message='You are not authorized')
4241

4342
client_id, client_secret = CredentialsManager().get_credentials()
4443
if not client_id or not client_secret:
45-
return _print_result(**failed_auth_check_args)
44+
return printer.print_result(failed_auth_check_res)
4645

4746
try:
48-
# TODO(MarshalX): This property performs HTTP request to refresh the token. This must be the method.
4947
if CycodeTokenBasedClient(client_id, client_secret).api_token:
50-
return _print_result(**passed_auth_check_args)
48+
return printer.print_result(passed_auth_check_res)
5149
except (NetworkError, HttpUnauthorizedError):
5250
if context.obj['verbose']:
5351
click.secho(f'Error: {traceback.format_exc()}', fg='red', nl=False)
5452

55-
return _print_result(**failed_auth_check_args)
53+
return printer.print_result(failed_auth_check_res)
5654

5755

58-
def _print_result(context: click.Context, content: dict, color: str) -> None:
59-
# the current impl of printers supports only results of scans
60-
if context.obj['output'] == 'text':
61-
return click.secho(content['message'], fg=color)
56+
def _handle_exception(context: click.Context, e: Exception):
57+
if context.obj['verbose']:
58+
click.secho(f'Error: {traceback.format_exc()}', fg='red', nl=False)
6259

63-
return click.echo(json.dumps({'result': content['success'], 'message': content['message']}))
60+
errors: CliErrors = {
61+
AuthProcessError: CliError(
62+
code='auth_error',
63+
message='Authentication failed. Please try again later using the command `cycode auth`'
64+
),
65+
NetworkError: CliError(
66+
code='cycode_error',
67+
message='Authentication failed. Please try again later using the command `cycode auth`'
68+
),
69+
}
6470

71+
error = errors.get(type(e))
72+
if error:
73+
return ConsolePrinter(context).print_error(error)
6574

66-
def _handle_exception(context: click.Context, e: Exception):
67-
verbose = context.obj["verbose"]
68-
if verbose:
69-
click.secho(f'Error: {traceback.format_exc()}', fg='red', nl=False)
70-
if isinstance(e, AuthProcessError):
71-
click.secho('Authentication failed. Please try again later using the command `cycode auth`',
72-
fg='red', nl=False)
73-
elif isinstance(e, NetworkError):
74-
click.secho('Authentication failed. Please try again later using the command `cycode auth`',
75-
fg='red', nl=False)
76-
elif isinstance(e, click.ClickException):
75+
if isinstance(e, click.ClickException):
7776
raise e
78-
else:
79-
raise click.ClickException(str(e))
77+
78+
raise click.ClickException(str(e))

cycode/cli/code_scanner.py

Lines changed: 16 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from typing import Type, NamedTuple
2-
31
import click
42
import json
53
import logging
@@ -14,8 +12,8 @@
1412

1513
from halo import Halo
1614

17-
from cycode.cli.printers import ResultsPrinter
18-
from cycode.cli.models import Document, DocumentDetections, Severity
15+
from cycode.cli.printers import ConsolePrinter
16+
from cycode.cli.models import Document, DocumentDetections, Severity, CliError, CliErrors
1917
from cycode.cli.ci_integrations import get_commit_range
2018
from cycode.cli.consts import *
2119
from cycode.cli.config import configuration_manager
@@ -446,14 +444,15 @@ def print_scan_details(scan_details_response: ScanDetailsResponse):
446444
if scan_details_response.message is not None:
447445
logger.info(f"Scan message: {scan_details_response.message}")
448446

449-
def print_results(context: click.Context, document_detections_list: List[DocumentDetections]):
450-
output_type = context.obj['output']
451-
printer = ResultsPrinter()
452-
printer.print_results(context, document_detections_list, output_type)
447+
448+
def print_results(context: click.Context, document_detections_list: List[DocumentDetections]) -> None:
449+
printer = ConsolePrinter(context)
450+
printer.print_scan_results(document_detections_list)
453451

454452

455-
def enrich_scan_result(scan_result: ZippedFileScanResult, documents_to_scan: List[Document]) -> \
456-
List[DocumentDetections]:
453+
def enrich_scan_result(
454+
scan_result: ZippedFileScanResult, documents_to_scan: List[Document]
455+
) -> List[DocumentDetections]:
457456
logger.debug('enriching scan result')
458457
document_detections_list = []
459458
for detections_per_file in scan_result.detections_per_file:
@@ -819,49 +818,39 @@ def _is_subpath_of_cycode_configuration_folder(filename: str) -> bool:
819818
or filename.endswith(ConfigFileManager.get_config_file_route())
820819

821820

822-
class CliScanError(NamedTuple):
823-
soft_fail: bool
824-
code: str
825-
message: str
826-
827-
828-
CliScanErrors = Dict[Type[Exception], CliScanError]
829-
830-
831821
def _handle_exception(context: click.Context, e: Exception):
832822
context.obj['did_fail'] = True
833823

834824
if context.obj['verbose']:
835825
click.secho(f'Error: {traceback.format_exc()}', fg='red', nl=False)
836826

837-
# TODO(MarshalX): Create global CLI errors database and move this
838-
errors: CliScanErrors = {
839-
NetworkError: CliScanError(
827+
errors: CliErrors = {
828+
NetworkError: CliError(
840829
soft_fail=True,
841830
code='cycode_error',
842831
message='Cycode was unable to complete this scan. '
843832
'Please try again by executing the `cycode scan` command'
844833
),
845-
ScanAsyncError: CliScanError(
834+
ScanAsyncError: CliError(
846835
soft_fail=True,
847836
code='scan_error',
848837
message='Cycode was unable to complete this scan. '
849838
'Please try again by executing the `cycode scan` command'
850839
),
851-
HttpUnauthorizedError: CliScanError(
840+
HttpUnauthorizedError: CliError(
852841
soft_fail=True,
853842
code='auth_error',
854843
message='Unable to authenticate to Cycode, your token is either invalid or has expired. '
855844
'Please re-generate your token and reconfigure it by running the `cycode configure` command'
856845
),
857-
ZipTooLargeError: CliScanError(
846+
ZipTooLargeError: CliError(
858847
soft_fail=True,
859848
code='zip_too_large_error',
860849
message='The path you attempted to scan exceeds the current maximum scanning size cap (10MB). '
861850
'Please try ignoring irrelevant paths using the ‘cycode ignore --by-path’ command '
862851
'and execute the scan again'
863852
),
864-
InvalidGitRepositoryError: CliScanError(
853+
InvalidGitRepositoryError: CliError(
865854
soft_fail=False,
866855
code='invalid_git_error',
867856
message='The path you supplied does not correlate to a git repository. '
@@ -875,22 +864,14 @@ def _handle_exception(context: click.Context, e: Exception):
875864
if error.soft_fail is True:
876865
context.obj['soft_fail'] = True
877866

878-
return _print_error(context, error)
867+
return ConsolePrinter(context).print_error(error)
879868

880869
if isinstance(e, click.ClickException):
881870
raise e
882871

883872
raise click.ClickException(str(e))
884873

885874

886-
def _print_error(context: click.Context, error: CliScanError) -> None:
887-
# TODO(MarshalX): Extend functionality of CLI printers and move this
888-
if context.obj['output'] == 'text':
889-
click.secho(error.message, fg='red', nl=False)
890-
elif context.obj['output'] == 'json':
891-
click.echo(json.dumps({'error': error.code, 'message': error.message}, ensure_ascii=False))
892-
893-
894875
def _report_scan_status(context: click.Context, scan_type: str, scan_id: str, scan_completed: bool,
895876
output_detections_count: int, all_detections_count: int, files_to_scan_count: int,
896877
zip_size: int, command_scan_type: str, error_message: Optional[str]):

cycode/cli/models.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from enum import Enum
2-
from typing import List
2+
from typing import List, NamedTuple, Dict, Type
3+
34
from cycode.cyclient.models import Detection
45

56

@@ -43,3 +44,17 @@ def try_get_value(name: str) -> any:
4344
return None
4445

4546
return Severity[name].value
47+
48+
49+
class CliError(NamedTuple):
50+
code: str
51+
message: str
52+
soft_fail: bool = False
53+
54+
55+
CliErrors = Dict[Type[Exception], CliError]
56+
57+
58+
class CliResult(NamedTuple):
59+
success: bool
60+
message: str

cycode/cli/printers/__init__.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
1-
from .json_printer import JsonPrinter
2-
from .text_printer import TextPrinter
3-
from .results_printer import ResultsPrinter
1+
from cycode.cli.printers.console_printer import ConsolePrinter
42

5-
6-
__all__ = [
7-
'JsonPrinter',
8-
'TextPrinter',
9-
'ResultsPrinter'
10-
]
3+
__all__ = ['ConsolePrinter']

cycode/cli/printers/base_printer.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,23 @@
33

44
import click
55

6-
from cycode.cli.models import DocumentDetections
6+
from cycode.cli.models import DocumentDetections, CliResult, CliError
77

88

99
class BasePrinter(ABC):
10-
1110
context: click.Context
1211

1312
def __init__(self, context: click.Context):
1413
self.context = context
1514

1615
@abstractmethod
17-
def print_results(self, results: List[DocumentDetections]):
16+
def print_scan_results(self, results: List[DocumentDetections]) -> None:
17+
pass
18+
19+
@abstractmethod
20+
def print_result(self, result: CliResult) -> None:
21+
pass
22+
23+
@abstractmethod
24+
def print_error(self, error: CliError) -> None:
1825
pass
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import click
2+
from typing import List, TYPE_CHECKING
3+
4+
from cycode.cli.consts import SCA_SCAN_TYPE
5+
from cycode.cli.exceptions.custom_exceptions import CycodeError
6+
from cycode.cli.models import DocumentDetections, CliResult, CliError
7+
from cycode.cli.printers.table_printer import TablePrinter
8+
from cycode.cli.printers.json_printer import JsonPrinter
9+
from cycode.cli.printers.text_printer import TextPrinter
10+
11+
if TYPE_CHECKING:
12+
from cycode.cli.printers.base_printer import BasePrinter
13+
14+
15+
class ConsolePrinter:
16+
_AVAILABLE_PRINTERS = {
17+
'text': TextPrinter,
18+
'json': JsonPrinter,
19+
'text_sca': TablePrinter
20+
}
21+
22+
def __init__(self, context: click.Context):
23+
self.context = context
24+
self.output_type = self.context.obj.get('output')
25+
26+
self._printer_class = self._AVAILABLE_PRINTERS.get(self.output_type)
27+
if self._printer_class is None:
28+
raise CycodeError(f'"{self.output_type}" output type is not supported.')
29+
30+
def print_scan_results(self, detections_results_list: List[DocumentDetections]) -> None:
31+
printer = self._get_scan_printer()
32+
printer.print_scan_results(detections_results_list)
33+
34+
def _get_scan_printer(self) -> 'BasePrinter':
35+
scan_type = self.context.obj.get('scan_type')
36+
37+
printer_class = self._printer_class
38+
if scan_type == SCA_SCAN_TYPE and self.output_type == 'text':
39+
printer_class = TablePrinter
40+
41+
return printer_class(self.context)
42+
43+
def print_result(self, result: CliResult) -> None:
44+
self._printer_class(self.context).print_result(result)
45+
46+
def print_error(self, error: CliError) -> None:
47+
self._printer_class(self.context).print_error(error)

0 commit comments

Comments
 (0)