Skip to content

Commit 9dd8f1a

Browse files
committed
feat: add dependency resolution for Python
Signed-off-by: behnazh-w <behnaz.hassanshahi@oracle.com>
1 parent 8b6fa3f commit 9dd8f1a

40 files changed

+5700
-10887
lines changed

docker/user.sh

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/bin/bash
22

3-
# Copyright (c) 2022 - 2023, Oracle and/or its affiliates. All rights reserved.
3+
# Copyright (c) 2022 - 2024, Oracle and/or its affiliates. All rights reserved.
44
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.
55

66
# We update the GID and UID of the existing macaron user in the container
@@ -52,6 +52,24 @@ then
5252
mkdir --parents "$HOME"/output
5353
fi
5454

55+
# Prepare the python virtual environment. We copy the mounted directory to `.python_venv` to fix the symbolic
56+
# links to the Python interpreter in the container without affecting the files on host.
57+
if [[ -d "$HOME/python_venv" ]];
58+
then
59+
cp -r "$HOME/python_venv" "$HOME/.python_venv"
60+
fi
61+
python_binaries=(
62+
"python"
63+
"python3"
64+
"python3.11"
65+
)
66+
for p in "${python_binaries[@]}"; do
67+
if [[ -f "$HOME/.venv/bin/${p}" ]];
68+
then
69+
ln -sf "$HOME/.venv/bin/${p}" "$HOME/.python_venv/bin/${p}"
70+
fi
71+
done
72+
5573
# The directory that could be mounted to the host machine file systems should
5674
# have the owner as the current user in the host machine.
5775
chown --recursive macaron:macaron "$HOME"/.m2

docs/source/pages/developers_guide/apidoc/macaron.dependency_analyzer.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ macaron.dependency\_analyzer.cyclonedx\_mvn module
3333
:undoc-members:
3434
:show-inheritance:
3535

36-
macaron.dependency\_analyzer.dependency\_resolver module
37-
--------------------------------------------------------
36+
macaron.dependency\_analyzer.cyclonedx\_python module
37+
-----------------------------------------------------
3838

39-
.. automodule:: macaron.dependency_analyzer.dependency_resolver
39+
.. automodule:: macaron.dependency_analyzer.cyclonedx_python
4040
:members:
4141
:undoc-members:
4242
:show-inheritance:

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ dependencies = [
3131
"defusedxml >=0.7.1,<1.0.0",
3232
"packageurl-python >= 0.11.1,<1.0.0",
3333
"ruamel.yaml >= 0.18.6,<1.0.0",
34-
"jsonschema >= 4.22.0,<5.0.0"
34+
"jsonschema >= 4.22.0,<5.0.0",
35+
"cyclonedx-bom >=4.0.0,<5.0.0",
36+
"cyclonedx-python-lib >=7.3.4,<8.0.0",
3537
]
3638
keywords = []
3739
# https://pypi.org/classifiers/

scripts/dev_scripts/integration_tests.sh

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ COMPARE_VSA=$WORKSPACE/tests/vsa/compare_vsa.py
1414
TEST_REPO_FINDER=$WORKSPACE/tests/e2e/repo_finder/repo_finder.py
1515
TEST_COMMIT_FINDER=$WORKSPACE/tests/e2e/repo_finder/commit_finder.py
1616
RUN_MACARON="python -m macaron -o $WORKSPACE/output"
17+
MAKE_VENV="python -m venv"
1718
RESULT_CODE=0
1819
UPDATE=0
1920

@@ -262,9 +263,9 @@ echo "apache/maven: Analyzing using a CycloneDx SBOM file of a software componen
262263
echo -e "----------------------------------------------------------------------------------\n"
263264
SBOM_FILE=$WORKSPACE/tests/dependency_analyzer/cyclonedx/resources/private_mirror_apache_maven.json
264265
DEP_EXPECTED=$WORKSPACE/tests/dependency_analyzer/expected_results/private_mirror_apache_maven.json
265-
DEP_RESULT=$WORKSPACE/output/reports/private-domain_com/apache/maven/dependencies.json
266+
DEP_RESULT=$WORKSPACE/output/reports/maven/private_apache_maven/maven/dependencies.json
266267

267-
$RUN_MACARON analyze -purl pkg:private-domain.com/apache/maven -sbom "$SBOM_FILE" || log_fail
268+
$RUN_MACARON analyze -purl pkg:maven/private.apache.maven/maven@4.0.0-alpha-1-SNAPSHOT?type=pom -sbom "$SBOM_FILE" || log_fail
268269

269270
check_or_update_expected_output $COMPARE_DEPS $DEP_RESULT $DEP_EXPECTED || log_fail
270271

@@ -457,6 +458,27 @@ $RUN_MACARON -lr $WORKSPACE/output/git_repos/github_com analyze -rp apache/maven
457458
check_or_update_expected_output $COMPARE_DEPS $DEP_RESULT $DEP_EXPECTED || log_fail
458459
check_or_update_expected_output $COMPARE_JSON_OUT $JSON_RESULT $JSON_EXPECTED || log_fail
459460

461+
echo -e "\n----------------------------------------------------------------------------------"
462+
echo "pkg:pypi/django@5.0.6: Analyzing the dependencies with virtual env provided as input."
463+
echo -e "----------------------------------------------------------------------------------\n"
464+
# Prepare the virtual environment.
465+
VIRTUAL_ENV_PATH=$WORKSPACE/.django_venv
466+
$MAKE_VENV "$VIRTUAL_ENV_PATH"
467+
"$VIRTUAL_ENV_PATH"/bin/pip install django==5.0.6
468+
$RUN_MACARON analyze -purl pkg:pypi/django@5.0.6 --python-venv "$VIRTUAL_ENV_PATH" || log_fail
469+
470+
# Check the dependencies using the policy engine.
471+
RUN_POLICY="macaron verify-policy"
472+
POLICY_FILE=$WORKSPACE/tests/policy_engine/resources/policies/django/test_dependencies.dl
473+
POLICY_RESULT=$WORKSPACE/output/policy_report.json
474+
POLICY_EXPECTED=$WORKSPACE/tests/policy_engine/expected_results/django/test_dependencies.json
475+
476+
$RUN_POLICY -f "$POLICY_FILE" -d "$WORKSPACE/output/macaron.db" || log_fail
477+
check_or_update_expected_output $COMPARE_POLICIES "$POLICY_RESULT" "$POLICY_EXPECTED" || log_fail
478+
479+
# Clean up and remove the virtual environment.
480+
rm -rf "$VIRTUAL_ENV_PATH"
481+
460482
echo -e "\n----------------------------------------------------------------------------------"
461483
echo "apache/maven: Analyzing with local paths in configuration and without dependency resolution."
462484
echo -e "----------------------------------------------------------------------------------\n"

scripts/dev_scripts/integration_tests_docker.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ COMPARE_JSON_OUT=$WORKSPACE/tests/e2e/compare_e2e_result.py
1818
COMPARE_POLICIES=$WORKSPACE/tests/policy_engine/compare_policy_reports.py
1919
COMPARE_VSA=$WORKSPACE/tests/vsa/compare_vsa.py
2020
UNIT_TEST_SCRIPT=$WORKSPACE/scripts/dev_scripts/test_run_macaron_sh.py
21+
MAKE_VENV="python -m venv"
2122

2223
RESULT_CODE=0
2324

@@ -102,6 +103,26 @@ $RUN_MACARON_SCRIPT analyze -purl pkg:maven/apache/maven -rp https://github.com/
102103

103104
python $COMPARE_JSON_OUT $JSON_RESULT $JSON_EXPECTED || log_fail
104105

106+
echo -e "\n----------------------------------------------------------------------------------"
107+
echo "pkg:pypi/django@5.0.6: Analyzing the dependencies with virtual env provided as input."
108+
echo -e "----------------------------------------------------------------------------------\n"
109+
# Prepare the virtual environment.
110+
VIRTUAL_ENV_PATH=$WORKSPACE/.django_venv
111+
$MAKE_VENV "$VIRTUAL_ENV_PATH"
112+
"$VIRTUAL_ENV_PATH"/bin/pip install django==5.0.6
113+
$RUN_MACARON_SCRIPT analyze -purl pkg:pypi/django@5.0.6 --python-venv "$VIRTUAL_ENV_PATH" || log_fail
114+
115+
# Check the dependencies using the policy engine.
116+
POLICY_FILE=$WORKSPACE/tests/policy_engine/resources/policies/django/test_dependencies.dl
117+
POLICY_RESULT=$WORKSPACE/output/policy_report.json
118+
POLICY_EXPECTED=$WORKSPACE/tests/policy_engine/expected_results/django/test_dependencies.json
119+
120+
$RUN_MACARON_SCRIPT verify-policy -f "$POLICY_FILE" -d "$WORKSPACE/output/macaron.db" || log_fail
121+
python $COMPARE_POLICIES $POLICY_RESULT $POLICY_EXPECTED || log_fail
122+
123+
# Clean up and remove the virtual environment.
124+
rm -rf "$VIRTUAL_ENV_PATH"
125+
105126
echo -e "\n----------------------------------------------------------------------------------"
106127
echo "urllib3/urllib3: Analyzing the repo path when automatic dependency resolution is skipped."
107128
echo "The CUE expectation file is provided as a single file path."

scripts/release_scripts/run_macaron.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,10 @@ if [[ $command == "analyze" ]]; then
327327
arg_template_path="$2"
328328
shift
329329
;;
330+
--python-venv)
331+
python_venv_path="$2"
332+
shift
333+
;;
330334
*)
331335
rest_command+=("$1")
332336
;;
@@ -454,6 +458,16 @@ if [[ -n "${arg_prov_file:-}" ]]; then
454458
mount_file "-pf/--provenance-file" "$prov_file_path" "$prov_file_path_in_container" "ro,Z"
455459
fi
456460

461+
# Mount the Python virtual environment into ${MACARON_WORKSPACE}/python_venv.
462+
if [[ -n "${python_venv_path:-}" ]]; then
463+
python_venv_in_container="${MACARON_WORKSPACE}/python_venv"
464+
# We copy the mounted directory to `.python_venv` once the container starts running to
465+
# be able to make changes to the mounted files without affecting the files on host.
466+
argv_command+=("--python-venv" "${MACARON_WORKSPACE}/.python_venv")
467+
468+
mount_dir_ro "--python-venv" "$python_venv_path" "$python_venv_in_container"
469+
fi
470+
457471
# MACARON entrypoint - verify-policy command argvs
458472
# This is for macaron verify-policy command.
459473
# Determine the database path to be mounted into ${MACARON_WORKSPACE}/database/macaron.db

src/macaron/__main__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ def analyze_slsa_levels_single(analyzer_single_args: argparse.Namespace) -> None
5757
sys.exit(os.EX_OSFILE)
5858
global_config.load_expectation_files(analyzer_single_args.provenance_expectation)
5959

60+
# Set Python virtual environment path.
61+
if analyzer_single_args.python_venv is not None:
62+
if not os.path.exists(analyzer_single_args.python_venv):
63+
logger.critical(
64+
'The Python virtual environment path "%s" does not exist.', analyzer_single_args.python_venv
65+
)
66+
sys.exit(os.EX_OSFILE)
67+
global_config.load_python_venv(analyzer_single_args.python_venv)
68+
6069
analyzer = Analyzer(global_config.output_path, global_config.build_log_path)
6170

6271
# Initiate reporters.
@@ -412,6 +421,12 @@ def main(argv: list[str] | None = None) -> None:
412421
help=("The path to the Jinja2 html template (please make sure to use .html or .j2 extensions)."),
413422
)
414423

424+
single_analyze_parser.add_argument(
425+
"--python-venv",
426+
required=False,
427+
help=("The path to the Python virtual environment of the target software component."),
428+
)
429+
415430
# Dump the default values.
416431
sub_parser.add_parser(name="dump-defaults", description="Dumps the defaults.ini file to the output directory.")
417432

src/macaron/config/defaults.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ dep_tool_gradle = cyclonedx-gradle:1.7.4
4040
# This is the timeout (in seconds) to run the dependency resolver.
4141
timeout = 2400
4242
recursive = False
43+
# Determines whether the CycloneDX BOM file should be validated or not.
44+
validate = True
45+
# The CycloneDX schema version used for validation.
46+
schema = 1.6
4347

4448
# This is the repo finder script.
4549
[repofinder]

src/macaron/config/global_config.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,39 @@
1313
class GlobalConfig:
1414
"""Class for keeping track of global configurations."""
1515

16+
#: The provenance expectation paths.
1617
expectation_paths: list[str] = field(default_factory=list)
18+
19+
#: The path to the Macaron Python package.
1720
macaron_path: str = ""
21+
22+
#: The path to the output files.
1823
output_path: str = ""
24+
25+
#: The path to build logs directory.
1926
build_log_path: str = ""
27+
28+
#: The path to the local clone of the target repository.
2029
local_repos_path: str = ""
30+
31+
#: The GitHub token.
2132
gh_token: str = ""
33+
34+
#: The GitLab public token.
2235
gl_token: str = ""
36+
37+
#: The GitLab self-hosted token.
2338
gl_self_host_token: str = ""
39+
40+
#: The debug level.
2441
debug_level: int = logging.DEBUG
42+
43+
#: The path to resources directory.
2544
resources_path: str = ""
2645

46+
#: The path to Python virtual environment.
47+
python_venv_path: str = ""
48+
2749
def load(
2850
self,
2951
macaron_path: str,
@@ -79,6 +101,20 @@ def load_expectation_files(self, exp_path: str) -> None:
79101

80102
self.expectation_paths = exp_files
81103

104+
def load_python_venv(self, venv_path: str) -> None:
105+
"""
106+
Load Python virtual environment.
107+
108+
Parameters
109+
----------
110+
venv_path : str
111+
The path to the Python virtual environment of the target software component.
112+
"""
113+
if os.path.isdir(venv_path):
114+
logger.info("Successfully loaded %s", venv_path)
115+
116+
self.python_venv_path = str(os.path.abspath(venv_path))
117+
82118

83119
global_config = GlobalConfig()
84120
"""The object that can be imported and used globally."""
Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
11
# Copyright (c) 2022 - 2023, Oracle and/or its affiliates. All rights reserved.
22
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.
33

4-
"""This package contains the dependency resolvers for Java projects."""
5-
6-
from .dependency_resolver import ( # noqa: F401
7-
DependencyAnalyzer,
8-
DependencyAnalyzerError,
9-
DependencyInfo,
10-
DependencyTools,
11-
NoneDependencyAnalyzer,
12-
)
4+
"""This package contains the dependency resolvers."""

0 commit comments

Comments
 (0)