Skip to content

Commit 56e4352

Browse files
committed
Add support for multiple inputs in the run management command
Signed-off-by: tdruez <[email protected]>
1 parent 5382bbb commit 56e4352

File tree

2 files changed

+82
-14
lines changed

2 files changed

+82
-14
lines changed

scanpipe/management/commands/run.py

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
# ScanCode.io is a free software code scanning tool from nexB Inc. and others.
2121
# Visit https://github.com/aboutcode-org/scancode.io for support and download.
2222

23+
from collections import defaultdict
2324
from pathlib import Path
2425

2526
from django.core.management import call_command
@@ -42,12 +43,16 @@ def add_arguments(self, parser):
4243
help=(
4344
"One or more pipeline to run. "
4445
"The pipelines executed based on their given order. "
45-
'Groups can be provided using the "pipeline_name:option1,option2"'
46-
" syntax."
46+
'Groups can be provided using the "pipeline_name:option1,option2" '
47+
"syntax."
4748
),
4849
)
4950
parser.add_argument(
50-
"input_location", help="Input location: file, directory, and URL supported."
51+
"input_location",
52+
help=(
53+
"Input location: file, directory, and URL supported."
54+
'Multiple values can be provided using the "input1,input2" syntax.'
55+
),
5156
)
5257
parser.add_argument("--project", required=False, help="Project name.")
5358
parser.add_argument(
@@ -68,22 +73,38 @@ def handle(self, *args, **options):
6873
"pipeline": pipelines,
6974
"execute": True,
7075
"verbosity": 0,
76+
**self.get_input_options(input_location),
7177
}
7278

73-
if input_location.startswith(tuple(SCHEME_TO_FETCHER_MAPPING.keys())):
74-
create_project_options["input_urls"] = [input_location]
75-
else:
76-
input_path = Path(input_location)
77-
if not input_path.exists():
78-
raise CommandError(f"{input_location} not found.")
79-
if input_path.is_file():
80-
create_project_options["input_files"] = [input_location]
81-
else:
82-
create_project_options["copy_codebase"] = input_location
83-
8479
# Run the database migrations in case the database is not created or outdated.
8580
call_command("migrate", verbosity=0, interactive=False)
8681
# Create a project with proper inputs and execute the pipeline(s)
8782
call_command("create-project", project_name, **create_project_options)
8883
# Print the results for the specified format on stdout
8984
call_command("output", project=project_name, format=[output_format], print=True)
85+
86+
@staticmethod
87+
def get_input_options(input_location):
88+
"""
89+
Parse a comma-separated list of input locations and convert them into options
90+
for the `create-project` command.
91+
"""
92+
input_options = defaultdict(list)
93+
94+
for location in input_location.split(","):
95+
if location.startswith(tuple(SCHEME_TO_FETCHER_MAPPING.keys())):
96+
input_options["input_urls"].append(location)
97+
else:
98+
input_path = Path(location)
99+
if not input_path.exists():
100+
raise CommandError(f"{location} not found.")
101+
if input_path.is_file():
102+
input_options["input_files"].append(location)
103+
else:
104+
if input_options["copy_codebase"]:
105+
raise CommandError(
106+
"Only one codebase directory can be provided as input."
107+
)
108+
input_options["copy_codebase"] = location
109+
110+
return input_options

scanpipe/tests/test_commands.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,53 @@ def test_scanpipe_management_command_run(self):
984984
self.assertEqual("do_nothing", runs[1]["pipeline_name"])
985985
self.assertEqual(["Group1", "Group2"], runs[1]["selected_groups"])
986986

987+
@mock.patch("requests.sessions.Session.get")
988+
def test_scanpipe_management_command_run_multiple_inputs(self, mock_get):
989+
source_download_url = "https://example.com/z-source.zip#from"
990+
bin_download_url = "https://example.com/z-bin.zip#to"
991+
mock_get.side_effect = [
992+
make_mock_response(url=source_download_url),
993+
make_mock_response(url=bin_download_url),
994+
]
995+
996+
out = StringIO()
997+
inputs = [
998+
# copy_codebase option
999+
str(self.data / "codebase"),
1000+
# input_files option
1001+
str(self.data / "d2d" / "jars" / "from-flume-ng-node-1.9.0.zip"),
1002+
str(self.data / "d2d" / "jars" / "to-flume-ng-node-1.9.0.zip"),
1003+
# input_urls option
1004+
source_download_url,
1005+
bin_download_url,
1006+
]
1007+
joined_locations = ",".join(inputs)
1008+
with redirect_stdout(out):
1009+
call_command("run", "download_inputs", joined_locations)
1010+
1011+
json_data = json.loads(out.getvalue())
1012+
headers = json_data["headers"]
1013+
project_uuid = headers[0]["uuid"]
1014+
project = Project.objects.get(uuid=project_uuid)
1015+
1016+
expected = [
1017+
"from-flume-ng-node-1.9.0.zip",
1018+
"to-flume-ng-node-1.9.0.zip",
1019+
"z-bin.zip",
1020+
"z-source.zip",
1021+
]
1022+
self.assertEqual(expected, sorted(project.input_files))
1023+
1024+
input_sources = headers[0]["input_sources"]
1025+
self.assertEqual("z-bin.zip", input_sources[2]["filename"])
1026+
self.assertEqual("to", input_sources[2]["tag"])
1027+
self.assertEqual("z-source.zip", input_sources[3]["filename"])
1028+
self.assertEqual("from", input_sources[3]["tag"])
1029+
1030+
codebase_files = [path.name for path in project.codebase_path.glob("*")]
1031+
expected = ["a.txt", "b.txt", "c.txt"]
1032+
self.assertEqual(expected, sorted(codebase_files))
1033+
9871034
@mock.patch("scanpipe.models.Project.get_latest_output")
9881035
@mock.patch("requests.post")
9891036
@mock.patch("requests.sessions.Session.get")

0 commit comments

Comments
 (0)