Skip to content

Commit be95ba0

Browse files
committed
chore: use an env variable for host output path
Signed-off-by: behnazh-w <[email protected]>
1 parent 74072c0 commit be95ba0

File tree

16 files changed

+136
-48
lines changed

16 files changed

+136
-48
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ gradlew.bat
167167
.macaron
168168
reports
169169
output
170+
output_dir
170171
cdx_debug.json
171172
sbom_debug.json
172173
golang/internal/filewriter/mock_dir/result.json

scripts/release_scripts/run_macaron.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ IMAGE="ghcr.io/oracle/macaron"
5656
# Workspace directory inside of the container.
5757
MACARON_WORKSPACE="/home/macaron"
5858

59+
# Host output path outside the container.
60+
HOST_OUTPUT=""
61+
5962
# The entrypoint to run Macaron or the Policy Engine.
6063
# It it set by default to macaron.
6164
# We use an array here to preserve the arguments as provided by the user.
@@ -388,8 +391,10 @@ fi
388391
if [[ -n "${arg_output:-}" ]]; then
389392
output="${arg_output}"
390393
argv_main+=("--output" "${MACARON_WORKSPACE}/output/")
394+
HOST_OUTPUT="${arg_output}"
391395
else
392396
output=$(pwd)/output
397+
HOST_OUTPUT="output"
393398
echo "Setting default output directory to ${output}."
394399
fi
395400

@@ -659,6 +664,7 @@ docker run \
659664
--rm -i "${tty[@]}" \
660665
-e "USER_UID=${USER_UID}" \
661666
-e "USER_GID=${USER_GID}" \
667+
-e "HOST_OUTPUT=${HOST_OUTPUT}" \
662668
"${proxy_vars[@]}" \
663669
"${prod_vars[@]}" \
664670
"${mounts[@]}" \

src/macaron/__main__.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from macaron.config.global_config import global_config
2323
from macaron.console import RichConsoleHandler, access_handler
2424
from macaron.errors import ConfigurationError
25+
from macaron.output_reporter import find_report_output_path
2526
from macaron.output_reporter.reporter import HTMLReporter, JSONReporter, PolicyReporter
2627
from macaron.policy_engine.policy_engine import run_policy_engine, show_prelude
2728
from macaron.repo_finder import repo_finder
@@ -280,22 +281,22 @@ def verify_policy(verify_policy_args: argparse.Namespace) -> int:
280281
rich_handler = access_handler.get_handler()
281282
if vsa is not None:
282283
vsa_filepath = os.path.join(global_config.output_path, "vsa.intoto.jsonl")
283-
rich_handler.update_vsa(os.path.relpath(vsa_filepath, os.getcwd()))
284+
rich_handler.update_vsa(find_report_output_path(vsa_filepath))
284285
logger.info(
285286
"Generating the Verification Summary Attestation (VSA) to %s.",
286-
os.path.relpath(vsa_filepath, os.getcwd()),
287+
find_report_output_path(vsa_filepath),
287288
)
288289
logger.info(
289290
"To decode and inspect the payload, run `cat %s | jq -r '.payload' | base64 -d | jq`.",
290-
os.path.relpath(vsa_filepath, os.getcwd()),
291+
find_report_output_path(vsa_filepath),
291292
)
292293
try:
293294
with open(vsa_filepath, mode="w", encoding="utf-8") as file:
294295
file.write(json.dumps(vsa))
295296
except OSError as err:
296297
logger.error(
297298
"Could not generate the VSA to %s. Error: %s",
298-
os.path.relpath(vsa_filepath, os.getcwd()),
299+
find_report_output_path(vsa_filepath),
299300
err,
300301
)
301302
else:
@@ -372,7 +373,7 @@ def perform_action(action_args: argparse.Namespace) -> None:
372373
if not action_args.disable_rich_output:
373374
rich_handler.start("dump-defaults")
374375
# Create the defaults.ini file in the output dir and exit.
375-
create_defaults(action_args.output, os.getcwd())
376+
create_defaults(action_args.output)
376377
sys.exit(os.EX_OK)
377378

378379
case "verify-policy":
@@ -466,6 +467,9 @@ def main(argv: list[str] | None = None) -> None:
466467
global_config.gl_token = _get_token_from_dict_or_env("MCN_GITLAB_TOKEN", token_dict)
467468
global_config.gl_self_host_token = _get_token_from_dict_or_env("MCN_SELF_HOSTED_GITLAB_TOKEN", token_dict)
468469

470+
# Set the host output path, which would be set if Macaron is running inside a container.
471+
global_config.host_output_path = _get_host_output_path_env()
472+
469473
main_parser = argparse.ArgumentParser(prog="macaron")
470474

471475
main_parser.add_argument(
@@ -735,12 +739,12 @@ def main(argv: list[str] | None = None) -> None:
735739
if os.path.isdir(args.output):
736740
logger.info(
737741
"Setting the output directory to %s",
738-
os.path.relpath(args.output, os.getcwd()),
742+
find_report_output_path(args.output),
739743
)
740744
else:
741745
logger.info(
742746
"No directory at %s. Creating one ...",
743-
os.path.relpath(args.output, os.getcwd()),
747+
find_report_output_path(args.output),
744748
)
745749
os.makedirs(args.output)
746750

@@ -800,5 +804,17 @@ def _get_token_from_dict_or_env(token: str, token_dict: dict[str, str]) -> str:
800804
return token_dict[token] if token in token_dict else os.environ.get(token) or ""
801805

802806

807+
def _get_host_output_path_env() -> str:
808+
"""
809+
Get the host output path from the HOST_OUTPUT environment variable.
810+
811+
Returns
812+
-------
813+
str
814+
The HOST_OUTPUT environment variable or an empty string.
815+
"""
816+
return os.environ.get("HOST_OUTPUT") or ""
817+
818+
803819
if __name__ == "__main__":
804820
main()

src/macaron/build_spec_generator/build_spec_generator.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from macaron.build_spec_generator.reproducible_central.reproducible_central import gen_reproducible_central_build_spec
1818
from macaron.console import access_handler
1919
from macaron.errors import GenerateBuildSpecError
20+
from macaron.output_reporter import find_report_output_path
2021
from macaron.path_utils.purl_based_path import get_purl_based_dir
2122

2223
logger: logging.Logger = logging.getLogger(__name__)
@@ -120,17 +121,17 @@ def gen_build_spec_for_purl(
120121
logger.info(
121122
"Generating the %s format build spec to %s",
122123
build_spec_format.value,
123-
os.path.relpath(build_spec_file_path, os.getcwd()),
124+
find_report_output_path(build_spec_file_path),
124125
)
125126
rich_handler = access_handler.get_handler()
126-
rich_handler.update_gen_build_spec("Build Spec Path:", os.path.relpath(build_spec_file_path, os.getcwd()))
127+
rich_handler.update_gen_build_spec("Build Spec Path:", find_report_output_path(build_spec_file_path))
127128
try:
128129
with open(build_spec_file_path, mode="w", encoding="utf-8") as file:
129130
file.write(build_spec_content)
130131
except OSError as error:
131132
logger.error(
132133
"Could not create the build spec at %s. Error: %s",
133-
os.path.relpath(build_spec_file_path, os.getcwd()),
134+
find_report_output_path(build_spec_file_path),
134135
error,
135136
)
136137
return os.EX_OSERR

src/macaron/config/defaults.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import shutil
1111

1212
from macaron.console import access_handler
13+
from macaron.output_reporter import find_report_output_path
1314

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

@@ -138,15 +139,13 @@ def load_defaults(user_config_path: str) -> bool:
138139
return False
139140

140141

141-
def create_defaults(output_path: str, cwd_path: str) -> bool:
142+
def create_defaults(output_path: str) -> bool:
142143
"""Create the ``defaults.ini`` file at the Macaron's root dir for end users.
143144
144145
Parameters
145146
----------
146147
output_path : str
147148
The path where the ``defaults.ini`` will be created.
148-
cwd_path : str
149-
The path to the current working directory.
150149
151150
Returns
152151
-------
@@ -169,12 +168,12 @@ def create_defaults(output_path: str, cwd_path: str) -> bool:
169168
shutil.copy2(src_path, dest_path)
170169
logger.info(
171170
"Dumped the default values in %s.",
172-
os.path.relpath(os.path.join(output_path, "defaults.ini"), cwd_path),
171+
find_report_output_path(os.path.join(output_path, "defaults.ini")),
173172
)
174-
rich_handler.update_dump_defaults(os.path.relpath(dest_path, cwd_path))
173+
rich_handler.update_dump_defaults(find_report_output_path(dest_path))
175174
return True
176175
# We catch OSError to support errors on different platforms.
177176
except OSError as error:
178-
logger.error("Failed to create %s: %s.", os.path.relpath(dest_path, cwd_path), error)
177+
logger.error("Failed to create %s: %s.", find_report_output_path(dest_path), error)
179178
rich_handler.update_dump_defaults("[bold red]Failed[/]")
180179
return False

src/macaron/config/global_config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ class GlobalConfig:
4949
#: The path to the local .m2 Maven repository. This attribute is None if there is no available .m2 directory.
5050
local_maven_repo: str | None = None
5151

52+
#: The host output path, if Macaron is executed as a container.
53+
host_output_path: str = ""
54+
5255
def load(
5356
self,
5457
macaron_path: str,

src/macaron/dependency_analyzer/cyclonedx.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from macaron.config.target_config import Configuration
2727
from macaron.database.table_definitions import Component
2828
from macaron.errors import CycloneDXParserError, DependencyAnalyzerError
29+
from macaron.output_reporter import find_report_output_path
2930
from macaron.output_reporter.scm import SCMStatus
3031
from macaron.repo_finder.repo_finder import find_repo
3132
from macaron.repo_finder.repo_finder_enums import RepoFinderInfo
@@ -359,7 +360,7 @@ def resolve_dependencies(main_ctx: Any, sbom_path: str, recursive: bool = False)
359360
"Running %s version %s dependency analyzer on %s",
360361
dep_analyzer.tool_name,
361362
dep_analyzer.tool_version,
362-
os.path.relpath(main_ctx.component.repository.fs_path, os.getcwd()),
363+
find_report_output_path(main_ctx.component.repository.fs_path),
363364
)
364365

365366
log_path = os.path.join(
@@ -397,7 +398,7 @@ def resolve_dependencies(main_ctx: Any, sbom_path: str, recursive: bool = False)
397398
logger.info(
398399
"Stored dependency resolver log for %s to %s.",
399400
dep_analyzer.tool_name,
400-
os.path.relpath(log_path, os.getcwd()),
401+
find_report_output_path(log_path),
401402
)
402403

403404
# Use repo finder to find more repositories to analyze.
Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,59 @@
1-
# Copyright (c) 2022 - 2022, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2022 - 2025, 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/.
3+
4+
"""This module contains helper functions for reporting."""
5+
6+
import logging
7+
import os
8+
from pathlib import Path
9+
10+
from macaron.config.global_config import global_config
11+
12+
logger: logging.Logger = logging.getLogger(__name__)
13+
14+
15+
def find_report_output_path(file_name: str, host_output_path: str | None = None) -> str:
16+
"""
17+
Determine the output path for a report file.
18+
19+
If ``host_output_path`` is empty or None, returns the file path relative
20+
to the current working directory. Otherwise, prefixes the path (stripping
21+
the first directory component) with the provided container host output path.
22+
Returns empty string if path has no parts to strip.
23+
24+
Parameters
25+
----------
26+
file_name : str
27+
Path to the input file (absolute or relative).
28+
host_output_path : str | None
29+
Base output directory path.
30+
31+
Returns
32+
-------
33+
str
34+
Output path as string.
35+
36+
Examples
37+
--------
38+
>>> find_report_output_path("output/reports/maven/foo/bar", host_output_path=None)
39+
'output/reports/maven/foo/bar'
40+
>>> find_report_output_path("output/reports/maven/foo/bar", host_output_path="output_dir")
41+
'output_dir/reports/maven/foo/bar'
42+
>>> find_report_output_path("foo", host_output_path="output")
43+
'output/'
44+
>>> find_report_output_path("", host_output_path="output")
45+
''
46+
"""
47+
if not file_name:
48+
return ""
49+
if host_output_path is None:
50+
host_output_path = global_config.host_output_path
51+
try:
52+
file_path = Path(os.path.relpath(file_name, os.getcwd()))
53+
except (ValueError, OSError) as error:
54+
logger.debug("Failed to create path for %s: %s", file_name, error)
55+
return ""
56+
if not host_output_path:
57+
return str(file_path)
58+
59+
return os.path.join(host_output_path, file_path.relative_to(file_path.parts[0])).rstrip(".")

src/macaron/output_reporter/reporter.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import macaron.output_reporter.jinja2_extensions as jinja2_extensions # pylint: disable=consider-using-from-import
2222
from macaron.console import access_handler
23+
from macaron.output_reporter import find_report_output_path
2324
from macaron.output_reporter.results import Report
2425
from macaron.output_reporter.scm import SCMStatus
2526

@@ -62,13 +63,13 @@ def write_file(self, file_path: str, data: str) -> bool:
6263
"""
6364
try:
6465
with open(file_path, mode=self.mode, encoding=self.encoding) as file:
65-
logger.info("Writing to file %s", os.path.relpath(file_path, os.getcwd()))
66+
logger.info("Writing to file %s", find_report_output_path(file_path))
6667
file.write(data)
6768
return True
6869
except OSError as error:
6970
logger.error(
7071
"Cannot write to %s. Error: %s",
71-
os.path.relpath(file_path, os.getcwd()),
72+
find_report_output_path(file_path),
7273
error,
7374
)
7475
return False
@@ -128,15 +129,15 @@ def generate(self, target_dir: str, report: Report | dict) -> None:
128129
dep_file_name = os.path.join(target_dir, "dependencies.json")
129130
serialized_configs = list(report.get_serialized_configs())
130131
self.write_file(dep_file_name, json.dumps(serialized_configs, indent=self.indent))
131-
self.rich_handler.update_report_table("Dependencies Report", os.path.relpath(dep_file_name, os.getcwd()))
132+
self.rich_handler.update_report_table("Dependencies Report", find_report_output_path(dep_file_name))
132133

133134
for record in report.get_records():
134135
if record.context and record.status == SCMStatus.AVAILABLE:
135136
file_name = os.path.join(target_dir, f"{record.context.component.report_file_name}.json")
136137
json_data = json.dumps(record.get_dict(), indent=self.indent)
137138
self.write_file(file_name, json_data)
138139
self.rich_handler.update_report_table(
139-
"JSON Report", os.path.relpath(file_name, os.getcwd()), record.record_id
140+
"JSON Report", find_report_output_path(file_name), record.record_id
140141
)
141142
except TypeError as error:
142143
logger.critical("Cannot serialize output report to JSON: %s", error)
@@ -231,7 +232,7 @@ def generate(self, target_dir: str, report: Report | dict) -> None:
231232
html = self.template.render(deepcopy(record.get_dict()))
232233
self.write_file(file_name, html)
233234
self.rich_handler.update_report_table(
234-
"HTML Report", os.path.relpath(file_name, os.getcwd()), record.record_id
235+
"HTML Report", find_report_output_path(file_name), record.record_id
235236
)
236237
except TemplateSyntaxError as error:
237238
location = f"line {error.lineno}"
@@ -285,7 +286,7 @@ def generate(self, target_dir: str, report: Report | dict) -> None:
285286
json.dumps(report, indent=self.indent),
286287
)
287288
self.rich_handler.update_policy_report(
288-
os.path.relpath(os.path.join(target_dir, "policy_report.json"), os.getcwd())
289+
find_report_output_path(os.path.join(target_dir, "policy_report.json"))
289290
)
290291
except (TypeError, ValueError, OSError) as error:
291292
logger.critical("Cannot serialize the policy report to JSON: %s", error)

src/macaron/provenance/provenance_verifier.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from macaron.config.defaults import defaults
1818
from macaron.config.global_config import global_config
19+
from macaron.output_reporter import find_report_output_path
1920
from macaron.provenance.provenance_extractor import ProvenancePredicate, SLSAGithubGenericBuildDefinitionV01
2021
from macaron.provenance.provenance_finder import ProvenanceAsset
2122
from macaron.repo_finder.commit_finder import AbstractPurlType, determine_abstract_purl_type
@@ -336,7 +337,7 @@ def _verify_slsa(
336337
verified = "PASSED: SLSA verification passed" in output
337338
log_path = os.path.join(global_config.build_log_path, f"{os.path.basename(source_path)}.slsa_verifier.log")
338339
with open(log_path, mode="a", encoding="utf-8") as log_file:
339-
logger.info("Storing SLSA verifier output for %s to %s", asset_name, os.path.relpath(log_path, os.getcwd()))
340+
logger.info("Storing SLSA verifier output for %s to %s", asset_name, find_report_output_path(log_path))
340341
log_file.writelines(
341342
[f"SLSA verifier output for cmd: {' '.join(cmd)}\n", output, "--------------------------------\n"]
342343
)
@@ -359,7 +360,7 @@ def _verify_slsa(
359360
)
360361
with open(error_log_path, mode="a", encoding="utf-8") as log_file:
361362
logger.info(
362-
"Storing SLSA verifier log for%s to %s", asset_name, os.path.relpath(error_log_path, os.getcwd())
363+
"Storing SLSA verifier log for%s to %s", asset_name, find_report_output_path(error_log_path)
363364
)
364365
log_file.write(f"SLSA verifier output for cmd: {' '.join(cmd)}\n")
365366
log_file.writelines(errors)

0 commit comments

Comments
 (0)