diff --git a/pyproject.toml b/pyproject.toml index 5b3784c..30b3c39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ dependencies = [ "pyyaml", "beautifulsoup4", "fosslight_util>=2.2.2,<3.0.0", - "fosslight_source>=2.2.16,<3.0.0", + "fosslight_source>=2.3.5,<3.0.0", "fosslight_dependency>=4.1.31,<5.0.0", "fosslight_binary>=5.1.17,<6.0.0", ] diff --git a/src/fosslight_scanner/fosslight_scanner.py b/src/fosslight_scanner/fosslight_scanner.py index 72770f9..7542344 100755 --- a/src/fosslight_scanner/fosslight_scanner.py +++ b/src/fosslight_scanner/fosslight_scanner.py @@ -9,7 +9,6 @@ import logging import warnings import shutil -import shlex import subprocess import platform from pathlib import Path @@ -75,6 +74,29 @@ def _all_exclude_mode_for_scanner( ) +def _build_source_docker_command( + output_dir, path_to_scan, path_to_exclude=None, + kb_url="", kb_token="", ui_mode=False): + """Build docker argv for fosslight_source fallback when the package is not installed.""" + command = [ + "docker", "run", "-it", + "-v", f"{output_dir}:/app/output", + "fosslight", + "-p", path_to_scan, + "-o", "output", + ] + if path_to_exclude: + command.append("-e") + command.extend(path_to_exclude) + if kb_url: + command.extend(["--kb_url", kb_url]) + if kb_token: + command.extend(["--kb_token", kb_token]) + if ui_mode: + command.append("--ui") + return command + + def run_dependency(path_to_analyze, output_file_with_path, params="", path_to_exclude=[], formats=[], recursive_dep=False, all_exclude_mode=()): result = [] @@ -140,13 +162,14 @@ def source_analysis_wrapper(*args, **kwargs): kb_url = kwargs.pop('kb_url', '') kb_token = kwargs.pop('kb_token', '') formats = kwargs.pop('formats', []) + ui_mode = kwargs.pop('ui_mode', False) args = list(args) args.insert(2, source_write_json_file) args.insert(5, source_print_matched_text) args.insert(6, formats) return source_analysis(*args, selected_scanner=selected_scanner, time_out=source_time_out, - kb_url=kb_url, kb_token=kb_token, **kwargs) + kb_url=kb_url, kb_token=kb_token, ui_mode=ui_mode, **kwargs) def run_scanner(src_path, dep_arguments, output_path, keep_raw_data=False, @@ -241,6 +264,7 @@ def run_scanner(src_path, dep_arguments, output_path, keep_raw_data=False, kb_url=kb_url, kb_token=kb_token, formats=formats, + ui_mode=ui_mode, all_exclude_mode=_all_exclude_mode_for_scanner( excluded_path_with_default_exclusion, excluded_path_without_dot, @@ -253,14 +277,9 @@ def run_scanner(src_path, dep_arguments, output_path, keep_raw_data=False, else: # Run fosslight_source by using docker image output_rel_path = os.path.relpath(abs_path, os.getcwd()) - command = shlex.quote(f"docker run -it -v {_output_dir}:/app/output " - f"fosslight -p {output_rel_path} -o output") - if path_to_exclude: - command += f" -e {' '.join(path_to_exclude)}" - if kb_url: - command += f" --kb_url {shlex.quote(kb_url)}" - if kb_token: - command += f" --kb_token {shlex.quote(kb_token)}" + command = _build_source_docker_command( + _output_dir, output_rel_path, path_to_exclude, + kb_url, kb_token, ui_mode) command_result = subprocess.run(command, stdout=subprocess.PIPE, text=True) logger.info(f"Source Analysis Result:{command_result.stdout}") diff --git a/tests/test_fosslight_scanner.py b/tests/test_fosslight_scanner.py index c741a67..6a1a0b6 100644 --- a/tests/test_fosslight_scanner.py +++ b/tests/test_fosslight_scanner.py @@ -2,7 +2,10 @@ import openpyxl import pytest from pathlib import Path -from fosslight_scanner.fosslight_scanner import run_scanner, download_source, init, run_main, run_dependency +from fosslight_scanner.fosslight_scanner import ( + run_scanner, download_source, init, run_main, run_dependency, + _build_source_docker_command, +) from fosslight_util.oss_item import ScannerItem from fosslight_util.constant import FOSSLIGHT_BINARY, FOSSLIGHT_DEPENDENCY, FOSSLIGHT_SOURCE, SHEET_NAME_FOR_SCANNER @@ -42,6 +45,89 @@ def _get_sheet_row_count(xlsx_path: str, sheet_name: str) -> int: return row_count +def test_build_source_docker_command_includes_ui_and_optional_args(): + command = _build_source_docker_command( + output_dir="/tmp/out", + path_to_scan="src", + path_to_exclude=["vendor", "third_party"], + kb_url="http://kb.example", + kb_token="secret", + ui_mode=True, + ) + + assert command[:3] == ["docker", "run", "-it"] + assert command[3:5] == ["-v", "/tmp/out:/app/output"] + assert "fosslight" in command + assert command[command.index("-p") + 1] == "src" + assert command[command.index("-o") + 1] == "output" + e_idx = command.index("-e") + assert command[e_idx + 1:e_idx + 3] == ["vendor", "third_party"] + assert command[command.index("--kb_url") + 1] == "http://kb.example" + assert command[command.index("--kb_token") + 1] == "secret" + assert command[-1] == "--ui" + + +def test_build_source_docker_command_omits_optional_args_by_default(): + command = _build_source_docker_command("/tmp/out", "src") + + assert "-e" not in command + assert "--kb_url" not in command + assert "--kb_token" not in command + assert "--ui" not in command + + +def test_run_scanner_docker_fallback_passes_argv_and_ui(monkeypatch, tmp_path): + import subprocess + import fosslight_scanner.fosslight_scanner as scanner_mod + + monkeypatch.setattr(scanner_mod, "fosslight_source_installed", False) + monkeypatch.setattr(scanner_mod, "_output_dir", "fosslight_raw_data") + + captured = {} + + def fake_run(cmd, **kwargs): + captured["cmd"] = cmd + captured["kwargs"] = kwargs + return subprocess.CompletedProcess(cmd, 0, stdout="docker-ok", stderr="") + + monkeypatch.setattr(scanner_mod.subprocess, "run", fake_run) + + src_path = tmp_path / "test_src" + output_path = tmp_path / "output" + src_path.mkdir(parents=True, exist_ok=True) + output_path.mkdir(parents=True, exist_ok=True) + (src_path / "test_file.py").write_text("print('hi')\n") + + run_scanner( + src_path=str(src_path), + dep_arguments="", + output_path=str(output_path), + keep_raw_data=True, + run_src=True, + run_bin=False, + run_dep=False, + remove_src_data=False, + result_log={}, + output_files=["test_output"], + output_extensions=[".yaml"], + num_cores=1, + ui_mode=True, + path_to_exclude=["vendor"], + kb_url="http://kb.example", + kb_token="secret", + ) + + assert "cmd" in captured, "Docker fallback should call subprocess.run" + cmd = captured["cmd"] + assert isinstance(cmd, list), "Docker command must be passed as an argv list" + assert cmd[0] == "docker" + assert "--ui" in cmd + assert cmd[cmd.index("--kb_url") + 1] == "http://kb.example" + assert cmd[cmd.index("--kb_token") + 1] == "secret" + assert "vendor" in cmd + assert captured["kwargs"].get("text") is True + + def test_run_dependency(tmp_path): # given path_to_analyze = tmp_path / "test_project"