diff --git a/detect_secrets/core/usage.py b/detect_secrets/core/usage.py index b94c9e7b3..7644be1dd 100644 --- a/detect_secrets/core/usage.py +++ b/detect_secrets/core/usage.py @@ -107,12 +107,12 @@ def _add_initialize_baseline_argument(self): help='Pass in regex to specify ignored paths during initialization scan.', ) - # Pairing `--import` with `--scan` because it's only used for initialization. + # Pairing `--update` with `--scan` because it's only used for initialization. self.parser.add_argument( - '--import', + '--update', nargs=1, metavar='OLD_BASELINE_FILE', - help='Import settings from previous existing baseline.', + help='Update existing baseline by importing settings from it.', dest='import_filename', ) diff --git a/detect_secrets/main.py b/detect_secrets/main.py index 8449b1c00..8f0977b63 100644 --- a/detect_secrets/main.py +++ b/detect_secrets/main.py @@ -26,14 +26,17 @@ def main(argv=None): log.set_debug_level(args.verbose) if args.action == 'scan': - print( - json.dumps( - _perform_scan(args), - indent=2, - sort_keys=True, - ), + output = json.dumps( + _perform_scan(args), + indent=2, + sort_keys=True, ) + if args.import_filename: + _write_to_file(args.import_filename[0], output) + else: + print(output) + elif args.action == 'audit': audit.audit_baseline(args.filename[0]) @@ -53,6 +56,11 @@ def _perform_scan(args): elif old_baseline and old_baseline.get('exclude_regex'): args.exclude = old_baseline['exclude_regex'] + # If we have knowledge of an existing baseline file, we should use + # that knowledge and *not* scan that file. + if args.import_filename and args.exclude: + args.exclude += r'|^{}$'.format(args.import_filename[0]) + new_baseline = baseline.initialize( plugins, args.exclude, @@ -71,12 +79,22 @@ def _perform_scan(args): def _get_existing_baseline(import_filename): # Favors --import argument over stdin. if import_filename: - with open(import_filename[0]) as f: - return json.loads(f.read()) - + return _read_from_file(import_filename[0]) if not sys.stdin.isatty(): return json.loads(sys.stdin.read()) +def _read_from_file(filename): + """Used for mocking.""" + with open(filename[0]) as f: + return json.loads(f.read()) + + +def _write_to_file(filename, content): + """Used for mocking.""" + with open(filename, 'w') as f: + f.write(content) + + if __name__ == '__main__': sys.exit(main()) diff --git a/tests/main_test.py b/tests/main_test.py index a2adce6cd..513a68c3f 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -11,7 +11,6 @@ from detect_secrets.main import main from testing.factories import secrets_collection_factory from testing.mocks import Any -from testing.mocks import mock_open from testing.mocks import mock_printer @@ -83,18 +82,53 @@ def test_reads_from_stdin(self, mock_merge_baseline): ) def test_reads_old_baseline_from_file(self, mock_merge_baseline): - with mock_stdin(), mock_open( - json.dumps({'key': 'value'}), - 'detect_secrets.main.open', - ) as m: - assert main('scan --import old_baseline_file'.split()) == 0 - assert m.call_args[0][0] == 'old_baseline_file' + with mock_stdin(), mock.patch( + 'detect_secrets.main._read_from_file', + return_value={'key': 'value'}, + ) as m_read, mock.patch( + 'detect_secrets.main._write_to_file', + ) as m_write: + assert main('scan --update old_baseline_file'.split()) == 0 + assert m_read.call_args[0][0] == 'old_baseline_file' + assert m_write.call_args[0] == ('old_baseline_file', Any(str)) mock_merge_baseline.assert_called_once_with( {'key': 'value'}, Any(dict), ) + @pytest.mark.parametrize( + 'exclude_param, expected_regex', + [ + ( + '', + '^old_baseline_file$', + ), + ( + '--exclude "secrets/.*"', + 'secrets/.*|^old_baseline_file$', + ), + ], + ) + def test_old_baseline_ignored_with_update_flag( + self, + mock_baseline_initialize, + exclude_param, + expected_regex, + ): + with mock_stdin(), mock.patch( + 'detect_secrets.main._read_from_file', + return_value={}, + ), mock.patch( + # We don't want to be creating a file during test + 'detect_secrets.main._write_to_file', + ): + assert main( + 'scan --update old_baseline_file {}'.format( + exclude_param, + ).split(), + ) == 0 + @pytest.mark.parametrize( 'filename, expected_output', [