Skip to content

Commit

Permalink
CM-41217 - Implement SBT restore support for SCA (#259)
Browse files Browse the repository at this point in the history
  • Loading branch information
naftalicy authored Nov 7, 2024
1 parent bb22262 commit 84b298c
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 17 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ This guide will guide you through both installation and usage.
6. [Commit History Scan](#commit-history-scan)
1. [Commit Range Option](#commit-range-option)
7. [Pre-Commit Scan](#pre-commit-scan)
8. [Lock Restore Options](#lock-restore-options)
1. [SBT Scan](#sbt-scan)
2. [Scan Results](#scan-results)
1. [Show/Hide Secrets](#showhide-secrets)
2. [Soft Fail](#soft-fail)
Expand Down Expand Up @@ -496,6 +498,17 @@ After your install the pre-commit hook and, you may, on occasion, wish to skip s
`SKIP=cycode git commit -m <your commit message>`
### Lock Restore Options
#### SBT Scan
We use sbt-dependency-lock plugin to restore the lock file for SBT projects.
To disable lock restore in use `--no-restore` option.
Prerequisites
* sbt-dependency-lock Plugin: Install the plugin by adding the following line to `project/plugins.sbt`:
`addSbtPlugin("software.purpledragon" % "sbt-dependency-lock" % "1.5.1")`
## Scan Results
Each scan will complete with a message stating if any issues were found or not.
Expand Down
11 changes: 9 additions & 2 deletions cycode/cli/commands/scan/repository/repository_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,15 @@ def repository_command(context: click.Context, path: str, branch: str) -> None:
# FIXME(MarshalX): probably file could be tree or submodule too. we expect blob only
progress_bar.update(ScanProgressBarSection.PREPARE_LOCAL_FILES)

file_path = file.path if monitor else get_path_by_os(os.path.join(path, file.path))
documents_to_scan.append(Document(file_path, file.data_stream.read().decode('UTF-8', errors='replace')))
absolute_path = get_path_by_os(os.path.join(path, file.path))
file_path = file.path if monitor else absolute_path
documents_to_scan.append(
Document(
file_path,
file.data_stream.read().decode('UTF-8', errors='replace'),
absolute_path=absolute_path,
)
)

documents_to_scan = exclude_irrelevant_documents_to_scan(scan_type, documents_to_scan)

Expand Down
18 changes: 15 additions & 3 deletions cycode/cli/files_collector/sca/base_restore_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ def build_dep_tree_path(path: str, generated_file_name: str) -> str:


def execute_command(
command: List[str], file_name: str, command_timeout: int, dependencies_file_name: Optional[str] = None
command: List[str],
file_name: str,
command_timeout: int,
dependencies_file_name: Optional[str] = None,
working_directory: Optional[str] = None,
) -> Optional[str]:
try:
dependencies = shell(command=command, timeout=command_timeout)
dependencies = shell(command=command, timeout=command_timeout, working_directory=working_directory)
# Write stdout output to the file if output_file_path is provided
if dependencies_file_name:
with open(dependencies_file_name, 'w') as output_file:
Expand Down Expand Up @@ -51,18 +55,26 @@ def get_manifest_file_path(self, document: Document) -> str:
def try_restore_dependencies(self, document: Document) -> Optional[Document]:
manifest_file_path = self.get_manifest_file_path(document)
restore_file_path = build_dep_tree_path(document.path, self.get_lock_file_name())
working_directory_path = self.get_working_directory(document)

if self.verify_restore_file_already_exist(restore_file_path):
restore_file_content = get_file_content(restore_file_path)
else:
output_file_path = restore_file_path if self.create_output_file_manually else None
execute_command(
self.get_command(manifest_file_path), manifest_file_path, self.command_timeout, output_file_path
self.get_command(manifest_file_path),
manifest_file_path,
self.command_timeout,
output_file_path,
working_directory_path,
)
restore_file_content = get_file_content(restore_file_path)

return Document(restore_file_path, restore_file_content, self.is_git_diff)

def get_working_directory(self, document: Document) -> Optional[str]:
return None

@abstractmethod
def verify_restore_file_already_exist(self, restore_file_path: str) -> bool:
pass
Expand Down
Empty file.
25 changes: 25 additions & 0 deletions cycode/cli/files_collector/sca/sbt/restore_sbt_dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import os
from typing import List, Optional

from cycode.cli.files_collector.sca.base_restore_dependencies import BaseRestoreDependencies
from cycode.cli.models import Document

SBT_PROJECT_FILE_EXTENSIONS = ['sbt']
SBT_LOCK_FILE_NAME = 'build.sbt.lock'


class RestoreSbtDependencies(BaseRestoreDependencies):
def is_project(self, document: Document) -> bool:
return any(document.path.endswith(ext) for ext in SBT_PROJECT_FILE_EXTENSIONS)

def get_command(self, manifest_file_path: str) -> List[str]:
return ['sbt', 'dependencyLockWrite', '--verbose']

def get_lock_file_name(self) -> str:
return SBT_LOCK_FILE_NAME

def verify_restore_file_already_exist(self, restore_file_path: str) -> bool:
return os.path.isfile(restore_file_path)

def get_working_directory(self, document: Document) -> Optional[str]:
return os.path.dirname(document.absolute_path)
14 changes: 5 additions & 9 deletions cycode/cli/files_collector/sca/sca_code_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
from cycode.cli.files_collector.sca.base_restore_dependencies import BaseRestoreDependencies
from cycode.cli.files_collector.sca.maven.restore_gradle_dependencies import RestoreGradleDependencies
from cycode.cli.files_collector.sca.maven.restore_maven_dependencies import RestoreMavenDependencies
from cycode.cli.files_collector.sca.npm.restore_npm_dependencies import RestoreNpmDependencies
from cycode.cli.files_collector.sca.nuget.restore_nuget_dependencies import RestoreNugetDependencies
from cycode.cli.files_collector.sca.sbt.restore_sbt_dependencies import RestoreSbtDependencies
from cycode.cli.models import Document
from cycode.cli.utils.git_proxy import git_proxy
from cycode.cli.utils.path_utils import get_file_content, get_file_dir, get_path_from_context, join_paths
Expand All @@ -17,9 +16,7 @@
if TYPE_CHECKING:
from git import Repo

BUILD_GRADLE_DEP_TREE_TIMEOUT = 180
BUILD_NUGET_DEP_TREE_TIMEOUT = 180
BUILD_NPM_DEP_TREE_TIMEOUT = 180
BUILD_DEP_TREE_TIMEOUT = 180


def perform_pre_commit_range_scan_actions(
Expand Down Expand Up @@ -132,10 +129,9 @@ def add_dependencies_tree_document(

def restore_handlers(context: click.Context, is_git_diff: bool) -> List[BaseRestoreDependencies]:
return [
RestoreGradleDependencies(context, is_git_diff, BUILD_GRADLE_DEP_TREE_TIMEOUT),
RestoreMavenDependencies(context, is_git_diff, BUILD_GRADLE_DEP_TREE_TIMEOUT),
RestoreNugetDependencies(context, is_git_diff, BUILD_NUGET_DEP_TREE_TIMEOUT),
RestoreNpmDependencies(context, is_git_diff, BUILD_NPM_DEP_TREE_TIMEOUT),
RestoreGradleDependencies(context, is_git_diff, BUILD_DEP_TREE_TIMEOUT),
RestoreMavenDependencies(context, is_git_diff, BUILD_DEP_TREE_TIMEOUT),
RestoreSbtDependencies(context, is_git_diff, BUILD_DEP_TREE_TIMEOUT),
]


Expand Down
8 changes: 7 additions & 1 deletion cycode/cli/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@

class Document:
def __init__(
self, path: str, content: str, is_git_diff_format: bool = False, unique_id: Optional[str] = None
self,
path: str,
content: str,
is_git_diff_format: bool = False,
unique_id: Optional[str] = None,
absolute_path: Optional[str] = None,
) -> None:
self.path = path
self.content = content
self.is_git_diff_format = is_git_diff_format
self.unique_id = unique_id
self.absolute_path = absolute_path

def __repr__(self) -> str:
return 'path:{0}, content:{1}'.format(self.path, self.content)
Expand Down
8 changes: 6 additions & 2 deletions cycode/cli/utils/shell_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@
_SUBPROCESS_DEFAULT_TIMEOUT_SEC = 60


def shell(command: Union[str, List[str]], timeout: int = _SUBPROCESS_DEFAULT_TIMEOUT_SEC) -> Optional[str]:
def shell(
command: Union[str, List[str]],
timeout: int = _SUBPROCESS_DEFAULT_TIMEOUT_SEC,
working_directory: Optional[str] = None,
) -> Optional[str]:
logger.debug('Executing shell command: %s', command)

try:
result = subprocess.run( # noqa: S603
command, timeout=timeout, check=True, capture_output=True
command, cwd=working_directory, timeout=timeout, check=True, capture_output=True
)

return result.stdout.decode('UTF-8').strip()
Expand Down

0 comments on commit 84b298c

Please sign in to comment.