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

fix: Precommit config overwritten on init #572

Merged
merged 2 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one -
- [Usage](#usage)
- [Help](#help)
- [Init](#init)
- [Preserving Pre-Commit Config](#preserving-pre-commit-config)
- [Scan](#scan)
- [Scanned Files](#scanned-files)
- [PII Scan](#pii-scan)
Expand Down Expand Up @@ -116,6 +117,10 @@ All you need to do is run:

Running `secureli init` will allow seCureLI to detect the languages in your repo, install pre-commit, install all the appropriate pre-commit hooks for your local repo, run a scan for secrets in your local repo, and update the installed hooks.

#### Preserving Pre-Commit Config

If you have an existing pre-commit config file you want to preserve when running `secureli init`, you can use the `--preserve-precommit-config` flag. This is useful for example when checking out a repo with an existing pre-commit config file.

### Scan

To manually trigger a scan, run:
Expand Down
8 changes: 8 additions & 0 deletions secureli/actions/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def verify_install(
always_yes: bool,
files: list[Path],
action_source: install.ActionSource,
preserve_precommit_config: bool = False,
) -> install.VerifyResult:
"""
Installs, upgrades or verifies the current seCureLI installation
Expand All @@ -77,6 +78,8 @@ def verify_install(
:param always_yes: Assume "Yes" to all prompts
:param files: A List of files to scope the install to. This allows language
detection to run on only a selected list of files when scanning the repo.
:param action_source: The source of the action
:param preserve_precommit_config: If true, preserve the existing pre-commit configuration
"""

is_config_out_of_date = (
Expand Down Expand Up @@ -163,6 +166,7 @@ def verify_install(
newly_detected_languages,
always_yes,
preferred_config_path if pre_commit_to_preserve else None,
preserve_precommit_config,
)
else:
self.action_deps.echo.print(
Expand Down Expand Up @@ -195,13 +199,16 @@ def _install_secureli(
install_languages: list[str],
always_yes: bool,
pre_commit_config_location: Path = None,
preserve_precommit_config: bool = False,
) -> install.VerifyResult:
"""
Installs seCureLI into the given folder path and returns the new configuration
:param folder_path: The folder path to initialize the repo for
:param detected_languages: list of all languages found in the repo
:param install_languages: list of specific langugages to install secureli features for
:param always_yes: Assume "Yes" to all prompts
:param pre_commit_config_location: The location of the pre-commit config file
:param preserve_precommit_config: If true, preserve the existing pre-commit configuration
:return: The new SecureliConfig after install or None if installation did not complete
"""

Expand Down Expand Up @@ -230,6 +237,7 @@ def _install_secureli(
install_languages,
language_config_result,
new_install,
preserve_precommit_config,
)

for error_msg in metadata.linter_config_write_errors:
Expand Down
8 changes: 7 additions & 1 deletion secureli/actions/initializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,26 @@ def __init__(
super().__init__(action_deps)

def initialize_repo(
self, folder_path: Path, reset: bool, always_yes: bool
self,
folder_path: Path,
reset: bool,
always_yes: bool,
preserve_precommit_config: bool = False,
) -> VerifyResult:
"""
Initializes seCureLI for the specified folder path
:param folder_path: The folder path to initialize the repo for
:param reset: If true, disregard existing configuration and start fresh
:param always_yes: Assume "Yes" to all prompts
:param preserve_precommit_config: If true, preserve the existing pre-commit configuration
"""
verify_result = self.verify_install(
folder_path,
reset,
always_yes,
files=None,
action_source=ActionSource.INITIALIZER,
preserve_precommit_config=preserve_precommit_config,
)
if verify_result.outcome in ScanAction.halting_outcomes:
self.action_deps.logging.failure(LogAction.init, verify_result.outcome)
Expand Down
7 changes: 6 additions & 1 deletion secureli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,19 @@ def init(
help="Run secureli against a specific directory",
),
] = Path("."),
preserve_precommit_config: bool = Option(
False,
"--preserve-precommit-config",
help="Preserve the existing pre-commit configuration",
),
):
"""
Detect languages and initialize pre-commit hooks and linters for the project
"""
SecureliConfig.FOLDER_PATH = Path(directory)

init_result = container.initializer_action().initialize_repo(
Path(directory), reset, yes
Path(directory), reset, yes, preserve_precommit_config
)
if init_result.outcome in [
VerifyOutcome.UP_TO_DATE,
Expand Down
19 changes: 11 additions & 8 deletions secureli/modules/language_analyzer/language_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ def apply_support(
languages: list[str],
language_config_result: language.BuildConfigResult,
overwrite_pre_commit: bool,
preserve_precommit_config: bool = False,
) -> language.LanguageMetadata:
"""
Applies Secure Build support for the provided languages
:param languages: list of languages to provide support for
:param language_config_result: resulting config from language hook detection
:param overwrite_pre_commit: flag to determine if config should overwrite or append to config file
:param preserve_precommit_config: If true, preserve the existing pre-commit configuration
:raises LanguageNotSupportedError if support for the language is not provided
:return: Metadata including version of the language configuration that was just installed
as well as a secret-detection hook ID, if present.
Expand All @@ -58,14 +60,15 @@ def apply_support(
language_config_result.linter_configs
)

pre_commit_file_mode = "w" if overwrite_pre_commit else "a"
with open(path_to_pre_commit_file, pre_commit_file_mode) as f:
data = (
language_config_result.config_data
if overwrite_pre_commit
else language_config_result.config_data["repos"]
)
f.write(yaml.dump(data))
if not preserve_precommit_config:
pre_commit_file_mode = "w" if overwrite_pre_commit else "a"
with open(path_to_pre_commit_file, pre_commit_file_mode) as f:
data = (
language_config_result.config_data
if overwrite_pre_commit
else language_config_result.config_data["repos"]
)
f.write(yaml.dump(data))

# Add .secureli/ to the gitignore folder if needed
self.git_ignore.ignore_secureli_files()
Expand Down
51 changes: 50 additions & 1 deletion tests/modules/language_analyzer/test_language_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,56 @@ def mock_loader_side_effect(resource):
)

metadata = language_support_service.apply_support(
["RadLang"], build_config_result, overwrite_pre_commit=True
["RadLang"],
build_config_result,
overwrite_pre_commit=True,
)

assert metadata.security_hook_id == "baddie-finder"


def test_that_language_support_preserves_precommit_config(
language_support_service: language_support.LanguageSupportService,
mock_language_config_service: MagicMock,
mock_data_loader: MagicMock,
mock_open: MagicMock,
mock_pre_commit_hook: MagicMock,
):
def mock_loader_side_effect(resource):
return """
http://sample-repo.com/baddie-finder:
- baddie-finder
"""

mock_language_config_service.get_language_config.return_value = language.LanguagePreCommitResult(
language="Python",
version="abc123",
linter_config=language.LoadLinterConfigsResult(
successful=True,
linter_data=[{"filename": "test.txt", "settings": {}}],
),
config_data="""
repos:
- repo: http://sample-repo.com/baddie-finder
hooks:
- id: baddie-finder
""",
)

mock_data_loader.side_effect = mock_loader_side_effect

languages = ["RadLang"]
lint_languages = [*languages]

build_config_result = language_support_service.build_pre_commit_config(
languages, lint_languages
)

metadata = language_support_service.apply_support(
["RadLang"],
build_config_result,
overwrite_pre_commit=True,
preserve_precommit_config=True,
)

assert metadata.security_hook_id == "baddie-finder"
Expand Down