Skip to content

Commit 1e5b64b

Browse files
committed
CM-26891 - Add cycode status command
1 parent 56a773b commit 1e5b64b

File tree

5 files changed

+117
-31
lines changed

5 files changed

+117
-31
lines changed

cycode/cli/commands/auth/auth_command.py

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
import click
22

33
from cycode.cli.commands.auth.auth_manager import AuthManager
4+
from cycode.cli.commands.auth_common import get_authorization_info
45
from cycode.cli.exceptions.custom_exceptions import (
56
KNOWN_USER_FRIENDLY_REQUEST_ERRORS,
67
AuthProcessError,
7-
HttpUnauthorizedError,
8-
RequestHttpError,
98
)
109
from cycode.cli.models import CliError, CliErrors, CliResult
1110
from cycode.cli.printers import ConsolePrinter
1211
from cycode.cli.sentry import add_breadcrumb, capture_exception
13-
from cycode.cli.user_settings.credentials_manager import CredentialsManager
14-
from cycode.cli.utils.jwt_utils import get_user_and_tenant_ids_from_access_token
1512
from cycode.cyclient import logger
16-
from cycode.cyclient.cycode_token_based_client import CycodeTokenBasedClient
1713

1814

1915
@click.group(
@@ -49,35 +45,18 @@ def authorization_check(context: click.Context) -> None:
4945
add_breadcrumb('check')
5046

5147
printer = ConsolePrinter(context)
52-
53-
failed_auth_check_res = CliResult(success=False, message='Cycode authentication failed')
54-
55-
client_id, client_secret = CredentialsManager().get_credentials()
56-
if not client_id or not client_secret:
57-
printer.print_result(failed_auth_check_res)
48+
auth_info = get_authorization_info(context)
49+
if auth_info is None:
50+
printer.print_result(CliResult(success=False, message='Cycode authentication failed'))
5851
return
5952

60-
try:
61-
access_token = CycodeTokenBasedClient(client_id, client_secret).get_access_token()
62-
if not access_token:
63-
printer.print_result(failed_auth_check_res)
64-
return
65-
66-
user_id, tenant_id = get_user_and_tenant_ids_from_access_token(access_token)
67-
printer.print_result(
68-
CliResult(
69-
success=True,
70-
message='Cycode authentication verified',
71-
data={'user_id': user_id, 'tenant_id': tenant_id},
72-
)
53+
printer.print_result(
54+
CliResult(
55+
success=True,
56+
message='Cycode authentication verified',
57+
data={'user_id': auth_info.user_id, 'tenant_id': auth_info.tenant_id},
7358
)
74-
75-
return
76-
except (RequestHttpError, HttpUnauthorizedError):
77-
ConsolePrinter(context).print_exception()
78-
79-
printer.print_result(failed_auth_check_res)
80-
return
59+
)
8160

8261

8362
def _handle_exception(context: click.Context, e: Exception) -> None:

cycode/cli/commands/auth_common.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from typing import NamedTuple, Optional
2+
3+
import click
4+
5+
from cycode.cli.exceptions.custom_exceptions import HttpUnauthorizedError, RequestHttpError
6+
from cycode.cli.printers import ConsolePrinter
7+
from cycode.cli.user_settings.credentials_manager import CredentialsManager
8+
from cycode.cli.utils.jwt_utils import get_user_and_tenant_ids_from_access_token
9+
from cycode.cyclient.cycode_token_based_client import CycodeTokenBasedClient
10+
11+
12+
class AuthInfo(NamedTuple):
13+
user_id: str
14+
tenant_id: str
15+
16+
17+
def get_authorization_info(context: Optional[click.Context] = None) -> Optional[AuthInfo]:
18+
client_id, client_secret = CredentialsManager().get_credentials()
19+
if not client_id or not client_secret:
20+
return None
21+
22+
try:
23+
access_token = CycodeTokenBasedClient(client_id, client_secret).get_access_token()
24+
if not access_token:
25+
return None
26+
27+
user_id, tenant_id = get_user_and_tenant_ids_from_access_token(access_token)
28+
return AuthInfo(user_id=user_id, tenant_id=tenant_id)
29+
except (RequestHttpError, HttpUnauthorizedError):
30+
if context:
31+
ConsolePrinter(context).print_exception()
32+
33+
return None

cycode/cli/commands/main_cli.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from cycode.cli.commands.ignore.ignore_command import ignore_command
99
from cycode.cli.commands.report.report_command import report_command
1010
from cycode.cli.commands.scan.scan_command import scan_command
11+
from cycode.cli.commands.status.status_command import status_command
1112
from cycode.cli.commands.version.version_command import version_command
1213
from cycode.cli.consts import (
1314
CLI_CONTEXT_SETTINGS,
@@ -28,6 +29,7 @@
2829
'ignore': ignore_command,
2930
'auth': auth_command,
3031
'version': version_command,
32+
'status': status_command,
3133
},
3234
context_settings=CLI_CONTEXT_SETTINGS,
3335
)

cycode/cli/commands/status/__init__.py

Whitespace-only changes.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import dataclasses
2+
import json
3+
import platform
4+
5+
import click
6+
7+
from cycode import __version__
8+
from cycode.cli.commands.auth_common import get_authorization_info
9+
from cycode.cli.consts import PROGRAM_NAME
10+
from cycode.cli.user_settings.configuration_manager import ConfigurationManager
11+
12+
13+
@dataclasses.dataclass
14+
class Status:
15+
program: str
16+
version: str
17+
os: str
18+
arch: str
19+
python_version: str
20+
installation_id: str
21+
app_url: str
22+
api_url: str
23+
is_authenticated: bool
24+
user_id: str = None
25+
tenant_id: str = None
26+
27+
def as_text(self) -> str:
28+
message_parts = []
29+
for key, value in dataclasses.asdict(self).items():
30+
human_readable_key = key.replace('_', ' ').capitalize()
31+
message_parts.append(f'{human_readable_key}: {value}')
32+
33+
return '\n'.join(message_parts)
34+
35+
def as_json(self) -> str:
36+
return json.dumps(dataclasses.asdict(self))
37+
38+
39+
def get_cli_status() -> Status:
40+
configuration_manager = ConfigurationManager()
41+
auth_info = get_authorization_info()
42+
43+
# TODO: Add supported modules; add AI status
44+
45+
return Status(
46+
program=PROGRAM_NAME,
47+
version=__version__,
48+
os=platform.system(),
49+
arch=platform.machine(),
50+
python_version=platform.python_version(),
51+
installation_id=configuration_manager.get_or_create_installation_id(),
52+
app_url=configuration_manager.get_cycode_app_url(),
53+
api_url=configuration_manager.get_cycode_api_url(),
54+
is_authenticated=auth_info is not None,
55+
user_id=auth_info.user_id if auth_info else None,
56+
tenant_id=auth_info.tenant_id if auth_info else None,
57+
)
58+
59+
60+
61+
@click.command(short_help='Show the CLI status and exit.')
62+
@click.pass_context
63+
def status_command(context: click.Context) -> None:
64+
output = context.obj['output']
65+
66+
status = get_cli_status()
67+
message = status.as_text()
68+
if output == 'json':
69+
message = status.as_json()
70+
71+
click.echo(message, color=context.color)
72+
context.exit()

0 commit comments

Comments
 (0)