Skip to content
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
2 changes: 1 addition & 1 deletion scripts/dev_scripts/integration_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ python $COMPARE_JSON_OUT $JSON_RESULT $JSON_EXPECTED || log_fail

echo -e "\n----------------------------------------------------------------------------------"
echo "timyarkov/multibuild_test: Analyzing the repo path, the branch name and the commit digest"
echo "with dependency resolution using cyclonedx Gradle plugin (default)."
echo "with dependency resolution using cyclonedx Gradle and Maven plugins (defaults)."
echo -e "----------------------------------------------------------------------------------\n"
JSON_EXPECTED=$WORKSPACE/tests/e2e/expected_results/multibuild_test/multibuild_test.json
JSON_RESULT=$WORKSPACE/output/reports/github_com/timyarkov/multibuild_test/multibuild_test.json
Expand Down
2 changes: 0 additions & 2 deletions src/macaron/dependency_analyzer/cyclonedx_mvn.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ def get_cmd(self) -> list:
f"org.cyclonedx:cyclonedx-maven-plugin:{self.tool_version}:makeAggregateBom",
"-D",
"includeTestScope=true",
"-f",
self.repo_path,
]

def collect_dependencies(self, dir_path: str) -> dict[str, DependencyInfo]:
Expand Down
3 changes: 1 addition & 2 deletions src/macaron/slsa_analyzer/analyze_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from pydriller.git import Git

from macaron.database.table_definitions import RepositoryTable, SLSALevelTable
from macaron.slsa_analyzer.build_tool.base_build_tool import NoneBuildTool
from macaron.slsa_analyzer.checks.check_result import CheckResult, CheckResultType
from macaron.slsa_analyzer.git_service import BaseGitService
from macaron.slsa_analyzer.git_service.base_git_service import NoneGitService
Expand Down Expand Up @@ -117,7 +116,7 @@ def __init__(
# Add the data computed at runtime to the dynamic_data attribute.
self.dynamic_data: ChecksOutputs = ChecksOutputs(
git_service=NoneGitService(),
build_spec=BuildSpec(tool=NoneBuildTool()),
build_spec=BuildSpec(tools=[]),
ci_services=[],
is_inferred_prov=True,
expectation=None,
Expand Down
123 changes: 62 additions & 61 deletions src/macaron/slsa_analyzer/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
from macaron.slsa_analyzer import git_url
from macaron.slsa_analyzer.analyze_context import AnalyzeContext
from macaron.slsa_analyzer.build_tool import BUILD_TOOLS
from macaron.slsa_analyzer.build_tool.base_build_tool import NoneBuildTool

# To load all checks into the registry
from macaron.slsa_analyzer.checks import * # pylint: disable=wildcard-import,unused-wildcard-import # noqa: F401,F403
Expand Down Expand Up @@ -246,71 +245,74 @@ def resolve_dependencies(self, main_ctx: AnalyzeContext, sbom_path: str) -> dict

deps_resolved: dict[str, DependencyInfo] = {}

build_tool = main_ctx.dynamic_data["build_spec"]["tool"]
if not build_tool or isinstance(build_tool, NoneBuildTool):
logger.info("Unable to find a valid build tool.")
build_tools = main_ctx.dynamic_data["build_spec"]["tools"]
if not build_tools:
logger.info("Unable to find any valid build tools.")
return {}

try:
dep_analyzer = build_tool.get_dep_analyzer(main_ctx.repo_path)
except DependencyAnalyzerError as error:
logger.error("Unable to find a dependency analyzer: %s", error)
return {}
# Grab dependencies for each build tool, collate all into the deps_resolved
for tool in build_tools:
try:
dep_analyzer = tool.get_dep_analyzer(main_ctx.repo_path)
except DependencyAnalyzerError as error:
logger.error("Unable to find a dependency analyzer for %s: %s", tool.name, error)
continue

if isinstance(dep_analyzer, NoneDependencyAnalyzer):
logger.info(
"Dependency analyzer is not available for %s",
tool.name,
)
continue

if isinstance(dep_analyzer, NoneDependencyAnalyzer):
# Start resolving dependencies.
logger.info(
"Dependency analyzer is not available for %s",
main_ctx.dynamic_data["build_spec"]["tool"].name,
"Running %s version %s dependency analyzer on %s",
dep_analyzer.tool_name,
dep_analyzer.tool_version,
main_ctx.repo_path,
)
return {}

# Start resolving dependencies.
logger.info(
"Running %s version %s dependency analyzer on %s",
dep_analyzer.tool_name,
dep_analyzer.tool_version,
main_ctx.repo_path,
)
log_path = os.path.join(
global_config.build_log_path,
f"{main_ctx.repo_name}.{dep_analyzer.tool_name}.log",
)

log_path = os.path.join(
global_config.build_log_path,
f"{main_ctx.repo_name}.{dep_analyzer.tool_name}.log",
)
# Clean up existing SBOM files.
dep_analyzer.remove_sboms(main_ctx.repo_path)

commands = dep_analyzer.get_cmd()
working_dirs: Iterable[Path] = tool.get_build_dirs(main_ctx.repo_path)
for working_dir in working_dirs:
# Get the absolute path to use as the working dir in the subprocess.
working_dir = Path(main_ctx.repo_path).joinpath(working_dir)
try:
# Suppressing Bandit's B603 report because the repo paths are validated.
analyzer_output = subprocess.run( # nosec B603
commands,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=True,
cwd=str(working_dir),
timeout=defaults.getint("dependency.resolver", "timeout", fallback=1200),
)
with open(log_path, mode="a", encoding="utf-8") as log_file:
log_file.write(analyzer_output.stdout.decode("utf-8"))

# Clean up existing SBOM files.
dep_analyzer.remove_sboms(main_ctx.repo_path)
except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as error:
logger.error(error)
with open(log_path, mode="a", encoding="utf-8") as log_file:
log_file.write(error.output.decode("utf-8"))
except FileNotFoundError as error:
logger.error(error)

# We collect the generated SBOM as a best effort, even if the build exits with errors.
# TODO: add improvements to help the SBOM build succeed as much as possible.
# Update deps_resolved with new dependencies.
deps_resolved |= dep_analyzer.collect_dependencies(str(working_dir))

logger.info("Stored dependency resolver log for %s to %s.", dep_analyzer.tool_name, log_path)

commands = dep_analyzer.get_cmd()
working_dirs: Iterable[Path] = build_tool.get_build_dirs(main_ctx.repo_path)
for working_dir in working_dirs:
# Get the absolute path to use as the working dir in the subprocess.
working_dir = Path(main_ctx.repo_path).joinpath(working_dir)
try:
# Suppressing Bandit's B603 report because the repo paths are validated.
analyzer_output = subprocess.run( # nosec B603
commands,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=True,
cwd=str(working_dir),
timeout=defaults.getint("dependency.resolver", "timeout", fallback=1200),
)
with open(log_path, mode="a", encoding="utf-8") as log_file:
log_file.write(analyzer_output.stdout.decode("utf-8"))

except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as error:
logger.error(error)
with open(log_path, mode="a", encoding="utf-8") as log_file:
log_file.write(error.output.decode("utf-8"))
except FileNotFoundError as error:
logger.error(error)

# We collect the generated SBOM as a best effort, even if the build exits with errors.
# TODO: add improvements to help the SBOM build succeed as much as possible.
# Update deps_resolved with new dependencies.
deps_resolved |= dep_analyzer.collect_dependencies(str(working_dir))

logger.info("Stored dependency resolver log to %s.", log_path)
return deps_resolved

def run_single(self, config: Configuration, existing_records: Optional[dict[str, Record]] = None) -> Record:
Expand Down Expand Up @@ -649,12 +651,11 @@ def perform_checks(self, analyze_ctx: AnalyzeContext) -> dict[str, CheckResult]:

if build_tool.is_detected(analyze_ctx.git_obj.path):
logger.info("The repo uses %s build tool.", build_tool.name)
analyze_ctx.dynamic_data["build_spec"]["tool"] = build_tool
break
analyze_ctx.dynamic_data["build_spec"]["tools"].append(build_tool)

if not analyze_ctx.dynamic_data["build_spec"].get("tool"):
if not analyze_ctx.dynamic_data["build_spec"]["tools"]:
logger.info(
"Cannot discover any build tool for %s or the build tool is not supported.",
"Cannot discover any build tools for %s or the build tools found are not supported.",
analyze_ctx.repo_full_name,
)

Expand Down
62 changes: 1 addition & 61 deletions src/macaron/slsa_analyzer/build_tool/base_build_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from collections.abc import Iterable
from pathlib import Path

from macaron.dependency_analyzer import DependencyAnalyzer, NoneDependencyAnalyzer
from macaron.dependency_analyzer import DependencyAnalyzer

logger: logging.Logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -171,63 +171,3 @@ def get_build_dirs(self, repo_path: str) -> Iterable[Path]:

except StopIteration:
pass


class NoneBuildTool(BaseBuildTool):
"""This class can be used to initialize an empty build tool."""

def __init__(self) -> None:
"""Initialize instance."""
super().__init__(name="")

def is_detected(self, repo_path: str) -> bool:
"""Return True if this build tool is used in the target repo.

Parameters
----------
repo_path : str
The path to the target repo.

Returns
-------
bool
True if this build tool is detected, else False.
"""
return False

def prepare_config_files(self, wrapper_path: str, build_dir: str) -> bool:
"""Prepare the necessary wrapper files for running the build.

This method will return False if there is any errors happened during operation.

Parameters
----------
wrapper_path : str
The path where all necessary wrapper files are located.
build_dir : str
The path of the build dir. This is where all files are copied to.

Returns
-------
bool
True if succeed else False.
"""
return False

def load_defaults(self) -> None:
"""Load the default values from defaults.ini."""

def get_dep_analyzer(self, repo_path: str) -> DependencyAnalyzer:
"""Create an invalid DependencyAnalyzer for the empty build tool.

Parameters
----------
repo_path: str
The path to the target repo.

Returns
-------
DependencyAnalyzer
The DependencyAnalyzer object.
"""
return NoneDependencyAnalyzer()
Loading