Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle the case where exclude-signatures is a list of strings #372

Merged
merged 6 commits into from
Aug 18, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
Unreleased
----------------------

Bug fixes:
* [#372](https://github.com/godaddy/tartufo/pull/372) - Handle the case where exclude-signatures is a list of strings

v3.2.1 - 20 July 2022
----------------------

Expand Down
10 changes: 5 additions & 5 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ sphinx-autodoc-typehints = {version = "^1.12.0", optional = true}
sphinx-click = {version = "^3.0.2", optional = true}
sphinx-rtd-theme = {version = "^1.0.0", optional = true}
sphinxcontrib-spelling = {version = "^7.2.1", optional = true}
tomlkit = "^0.7.2"
tomlkit = "^0.11.1"
cached-property = "^1.5.2"

[tool.poetry.dev-dependencies]
Expand Down
64 changes: 48 additions & 16 deletions tartufo/commands/update_signatures.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
Optional,
Sequence,
Tuple,
Union,
)

import click
Expand All @@ -22,6 +23,18 @@
DeprecationSetT = MutableSet[Sequence[str]]


def unwrap_signature(data: Union[str, MutableMapping[str, str]]) -> str:
"""Handles the case where a signature can be a string or a dict

:param data: The string, or dictionary to pull the signature from
:returns: The unwrapped signature
"""
if isinstance(data, str):
return data

return data["signature"]


def scan_local_repo(
options: types.GlobalOptions,
repo_path: str,
Expand Down Expand Up @@ -101,13 +114,20 @@ def replace_deprecated_signatures(
updated = 0

for old_sig, new_sig in deprecations:
targets = functools.partial(lambda o, s: o == s["signature"], old_sig)
targets = functools.partial(lambda o, s: o == unwrap_signature(s), old_sig)
# Iterate all the deprecations and update them everywhere
# they are found in the exclude-signatures section of config
for target_signature in filter(targets, config_data["exclude_signatures"]):
for i, target_signature in enumerate(config_data["exclude_signatures"]):
if not targets(target_signature):
continue

updated += 1
click.echo(f"{updated}) {old_sig!r} -> {new_sig!r}")
target_signature["signature"] = new_sig

if isinstance(target_signature, str):
config_data["exclude_signatures"][i] = new_sig
else:
target_signature["signature"] = new_sig

return updated

Expand All @@ -120,16 +140,18 @@ def write_updated_signatures(
:param config_path: The path to the tartufo config file
:param config_data: The updated config data
"""
with open(str(config_path), "r") as file:
result = tomlkit.loads(file.read())
with open(str(config_path), "r+") as file:
file_content = file.read()
result = tomlkit.loads(file_content)
file.seek(0)

# Assign the new signatures and write it to the config
result["tool"]["tartufo"]["exclude-signatures"] = config_data[ # type: ignore
"exclude_signatures"
]
# Assign the new signatures and write it to the config
result["tool"]["tartufo"]["exclude-signatures"] = config_data[ # type: ignore
"exclude_signatures"
]

with open(str(config_path), "w") as file:
file.write(tomlkit.dumps(result))
file.truncate()


def remove_duplicated_entries(config_data: MutableMapping[str, Any]) -> int:
Expand All @@ -138,17 +160,19 @@ def remove_duplicated_entries(config_data: MutableMapping[str, Any]) -> int:

:param config_data: The config data to check for duplicates
"""
seen = set()
seen: MutableSet[str] = set()
count = 0

for i, exclude in enumerate(config_data["exclude_signatures"].copy()):
if exclude["signature"] in seen:
for i, exclude in enumerate(config_data["exclude_signatures"]):
signature = unwrap_signature(exclude)

if signature in seen:
# Remove this duplicated signature
del config_data["exclude_signatures"][i]
config_data["exclude_signatures"].pop(i)
count += 1
else:
# Mark this signature as seen
seen.add(exclude["signature"])
seen.add(signature)

return count

Expand Down Expand Up @@ -202,7 +226,15 @@ def main(
remove_duplicates: bool,
) -> GitRepoScanner:
"""Update deprecated signatures for a local repository."""
config_path, config_data = load_config_from_path(pathlib.Path(repo_path))
try:
config_path, config_data = load_config_from_path(pathlib.Path(repo_path))
except FileNotFoundError:
util.fail(
util.style_warning("No tartufo config found, exiting..."),
ctx,
code=0,
)

if not config_data.get("exclude_signatures"):
util.fail(
util.style_warning("No signatures found in configuration, exiting..."),
Expand Down
24 changes: 24 additions & 0 deletions tests/test_update_signatures.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ def test_with_no_signatures_in_config(
result.output, "No signatures found in configuration, exiting...\n"
)

@mock.patch("tartufo.commands.update_signatures.load_config_from_path")
def test_with_no_config(self, mock_load_config: mock.MagicMock) -> None:
mock_load_config.side_effect = FileNotFoundError()

runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(cli.main, ["update-signatures", "."])

mock_load_config.assert_called_once()
self.assertEqual(result.output, "No tartufo config found, exiting...\n")

@mock.patch("tartufo.commands.update_signatures.GitRepoScanner")
@mock.patch("tartufo.commands.update_signatures.load_config_from_path")
def test_with_no_deprecated_signatures(
Expand Down Expand Up @@ -280,6 +291,19 @@ def test_found_output_with_no_signatures(
mock_scan_local.assert_called_once()
self.assertEqual(result.output, "Found 0 deprecated signatures.\n")

def test_replace_deprecated_with_list_of_strings(self) -> None:
deprecations: Set[Sequence[str]] = set()
deprecations.update((("123", "abc"), ("456", "def")))
config_data = {"exclude_signatures": ["123", "456"]}
expected_result = {"exclude_signatures": ["abc", "def"]}

count = update_signatures.replace_deprecated_signatures(
deprecations, config_data
)

self.assertEqual(count, 2)
self.assertEqual(config_data, expected_result)

def test_remove_duplicated_entries(self) -> None:
initial_data = {
"exclude_signatures": [
Expand Down