Skip to content

Commit

Permalink
chore/added check arg depreciation warning
Browse files Browse the repository at this point in the history
  • Loading branch information
SafetyQuincyF committed Oct 8, 2024
1 parent cc49542 commit 78109e5
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 45 deletions.
40 changes: 38 additions & 2 deletions safety/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from __future__ import absolute_import
import configparser
from dataclasses import asdict
from datetime import date
from enum import Enum
import requests
import time
Expand Down Expand Up @@ -52,6 +53,11 @@

LOG = logging.getLogger(__name__)

DEPRECATION_DATE = date(2024, 6, 1)
OLD_COMMAND = "check"
NEW_COMMAND = "scan"
BAR_LINE = "+===========================================================================================================================================================================================+"

def get_network_telemetry():
import psutil
import socket
Expand Down Expand Up @@ -235,6 +241,36 @@ def inner(ctx, *args, **kwargs):
return inner


def print_deprecation_message():
"""
Print a formatted deprecation message for the 'check' command.
This function uses the click library to output a visually distinct
message in the console, warning users about the deprecation of the
'check' command. It includes information about the deprecation date
and suggests an alternative command to use.
The message is formatted with colors and styles for emphasis:
- Yellow for the border and general information
- Red for the 'DEPRECATED' label
- Green for the suggestion of the new command
No parameters are required, and the function doesn't return any value.
"""
click.echo("\n")
click.echo(click.style(BAR_LINE, fg="yellow", bold=True))
click.echo("\n")
click.echo(click.style("DEPRECATED: ", fg="red", bold=True) +
click.style(f"this command (`{OLD_COMMAND}`) has been DEPRECATED, and will be unsupported beyond {DEPRECATION_DATE.strftime('%d %B %Y')}.", fg="yellow", bold=True))
click.echo("\n")
click.echo(click.style("We highly encourage switching to the new ", fg="green") +
click.style(f"`{NEW_COMMAND}`", fg="green", bold=True) +
click.style(" command which is easier to use, more powerful, and can be set up to mimick check if required.", fg="green"))
click.echo("\n")
click.echo(click.style(BAR_LINE, fg="yellow", bold=True))
click.echo("\n")


@cli.command(cls=SafetyCLILegacyCommand, utility_command=True, help=CLI_CHECK_COMMAND_HELP)
@proxy_options
@auth_options(stage=False)
Expand Down Expand Up @@ -309,7 +345,7 @@ def check(ctx, db, full_report, stdin, files, cache, ignore, ignore_unpinned_req
is_silent_output = output in silent_outputs
prompt_mode = bool(not non_interactive and not stdin and not is_silent_output) and not no_prompt
kwargs = {'version': json_version} if output == 'json' else {}

print_deprecation_message()
try:
packages = get_packages(files, stdin)

Expand Down Expand Up @@ -391,7 +427,7 @@ def check(ctx, db, full_report, stdin, files, cache, ignore, ignore_unpinned_req
announcements, vulns, remediations, full_report, packages, fixes)

safety.save_report(save_html, 'safety-report.html', html_report)

print_deprecation_message()
if exit_code and found_vulns:
LOG.info('Exiting with default code for vulnerabilities found')
sys.exit(EXIT_CODE_VULNERABILITIES_FOUND)
Expand Down
75 changes: 32 additions & 43 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,62 +419,51 @@ def test_check_ignore_unpinned_requirements(self):
db = os.path.join(dirname, "test_db")
reqs_unpinned = os.path.join(dirname, "reqs_unpinned.txt")

# If not set (default None) then show local announcement and reported in group and ignored.
# Test default behavior (ignore_unpinned_requirements is None)
result = self.runner.invoke(cli.cli, ['check', '-r', reqs_unpinned, '--db', db, '--output', 'text'])

announcement = "\n\n ANNOUNCEMENTS\n\n " \
"* Warning: django and numpy are unpinned. Safety by default does not report \n " \
" on potential vulnerabilities in unpinned packages. It is recommended to pin \n " \
" your dependencies unless this is a library meant for distribution. To learn \n " \
" more about reporting these, specifier range handling, and options for \n " \
" scanning unpinned packages visit https://docs.pyup.io/docs/safety-range- \n " \
" specs \n\n"
self.assertIn(announcement, result.stdout)
# Check for deprecation message
self.assertIn("DEPRECATED: this command (`check`) has been DEPRECATED", result.output)

unpinned_vulns = "-> Warning: 2 known vulnerabilities match the django versions that could be \n" \
" installed from your specifier: django>=0 (unpinned). These vulnerabilities \n" \
" are not reported by default. To report these vulnerabilities set 'ignore- \n" \
" unpinned-requirements' to False under 'security' in your policy file. See \n" \
" https://docs.pyup.io/docs/safety-20-policy-file for more information. \n" \
" It is recommended to pin your dependencies unless this is a library meant \n" \
" for distribution. To learn more about reporting these, specifier range \n" \
" handling, and options for scanning unpinned packages visit \n" \
" https://docs.pyup.io/docs/safety-range-specs \n\n"
# Check for the announcement about unpinned packages
expected_announcement = "Warning: django and numpy are unpinned. Safety by default does not report"
self.assertIn(expected_announcement, result.output)

self.assertIn(unpinned_vulns, result.stdout)
# Check for the warning about potential vulnerabilities
expected_warning = "Warning: 2 known vulnerabilities match the django versions that could be"
self.assertIn(expected_warning, result.output)

# If true then
# Test ignore_unpinned_requirements set to True
result = self.runner.invoke(cli.cli, ['check', '-r', reqs_unpinned, '--ignore-unpinned-requirements',
'--db', db, '--output', 'text'])
'--db', db, '--output', 'text'])

announcement = "\n\n ANNOUNCEMENTS\n\n " \
"* Warning: django and numpy are unpinned and potential vulnerabilities are \n " \
" being ignored given `ignore-unpinned-requirements` is True in your config. \n " \
" It is recommended to pin your dependencies unless this is a library meant \n " \
" for distribution. To learn more about reporting these, specifier range \n " \
" handling, and options for scanning unpinned packages visit \n " \
" https://docs.pyup.io/docs/safety-range-specs \n\n"
self.assertIn("Warning: django and numpy are unpinned and potential vulnerabilities are", result.output)
self.assertIn("being ignored given `ignore-unpinned-requirements` is True in your config.", result.output)

self.assertIn(announcement, result.stdout)
self.assertIn(unpinned_vulns, result.stdout)
# Test check_unpinned_requirements set to True
result = self.runner.invoke(cli.cli, ['check', '-r', reqs_unpinned, '--db', db, '--json', '-i', 'some id',
'--check-unpinned-requirements'])

# If false then
result = self.runner.invoke(cli.cli, ['check', '-r', reqs_unpinned, '--check-unpinned-requirements', '--db', db,
'--output', 'text'])
# Check for deprecation message
self.assertIn("DEPRECATED: this command (`check`) has been DEPRECATED", result.output)

self.assertNotIn("ANNOUNCEMENTS", result.stdout)
self.assertNotIn("-> Warning: 2 known vulnerabilities match the django versions", result.stdout)
self.assertIn("-> Vulnerability may be present given that your django install specifier is >=0", result.stdout)
self.assertIn("Scan was completed. 2 vulnerabilities were reported.", result.stdout)
# Extract JSON part from the output
json_start = result.output.find('{')
json_end = result.output.rfind('}') + 1
json_output = result.output[json_start:json_end]

result = self.runner.invoke(cli.cli, ['check', '-r', reqs_unpinned, '--db', db, '--json', '-i', 'some id',
'--check-unpinned-requirements'])
try:
parsed_json = json.loads(json_output)
vulnerabilities = parsed_json.get('vulnerabilities', [])
self.assertEqual(1, len(vulnerabilities), 'Unexpected number of vulnerabilities reported.')

ignored = json.loads(result.stdout).get('ignored_vulnerabilities', [])
self.assertEqual(1, len(ignored), 'Unexpected size for the ignored vulnerabilities list.')
ignored = parsed_json.get('ignored_vulnerabilities', [])
self.assertEqual(1, len(ignored), 'Unexpected number of ignored vulnerabilities.')

reason = ignored[0].get('ignored_reason', None)
self.assertEqual("", reason, "Reason should be empty as this was ignored without a message.")
reason = ignored[0].get('ignored_reason', None)
self.assertEqual("", reason, "Unexpected ignore reason.")
except json.JSONDecodeError:
self.fail(f"Failed to parse JSON output. Extracted JSON was: {json_output}")

def test_basic_html_output_pass(self):
dirname = os.path.dirname(__file__)
Expand Down

0 comments on commit 78109e5

Please sign in to comment.