Skip to content

Commit 0ff317f

Browse files
Update inspect_manifest to accept archives (#1037)
* Add support for codebase/packages in inspect_manifest The inspect_manifest pipeline is now renamed to inspect_manifests and this supports uploading a whole package/codebase archive to find manifests and resolve all packages in them, as opposed to supporting only manifests to be uploaded. Reference: #1034 Signed-off-by: Ayan Sinha Mahapatra <[email protected]> * Add test for archives as inspect_manifest input Reference: #1034 Signed-off-by: Ayan Sinha Mahapatra <[email protected]> * Address review feedback Reference: #1037 Signed-off-by: Ayan Sinha Mahapatra <[email protected]> --------- Signed-off-by: Ayan Sinha Mahapatra <[email protected]>
1 parent e9b15e3 commit 0ff317f

File tree

4 files changed

+79
-19
lines changed

4 files changed

+79
-19
lines changed

scanpipe/pipelines/inspect_packages.py

+29-11
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,19 @@
2020
# ScanCode.io is a free software code scanning tool from nexB Inc. and others.
2121
# Visit https://github.com/nexB/scancode.io for support and download.
2222

23-
from scanpipe.pipelines import Pipeline
23+
from scanpipe.pipelines.scan_codebase import ScanCodebase
2424
from scanpipe.pipes import resolve
2525
from scanpipe.pipes import update_or_create_package
2626

2727

28-
class InspectPackages(Pipeline):
28+
class InspectPackages(ScanCodebase):
2929
"""
30-
Inspect one or more manifest files and resolve their associated packages.
30+
Inspect a codebase/package with one or more manifest files and
31+
resolve their associated packages.
32+
33+
Supports resolved packages for:
34+
- Python: using nexB/python-inspector, supports requirements.txt and
35+
setup.py manifests as input
3136
3237
Supports:
3338
- BOM: SPDX document, CycloneDX BOM, AboutCode ABOUT file
@@ -48,26 +53,39 @@ class InspectPackages(Pipeline):
4853
@classmethod
4954
def steps(cls):
5055
return (
56+
cls.copy_inputs_to_codebase_directory,
57+
cls.extract_archives,
58+
cls.collect_and_create_codebase_resources,
59+
cls.flag_ignored_resources,
5160
cls.get_manifest_inputs,
5261
cls.get_packages_from_manifest,
5362
cls.create_resolved_packages,
5463
)
5564

5665
def get_manifest_inputs(self):
5766
"""Locate all the manifest files from the project's input/ directory."""
58-
self.input_locations = [
59-
str(input.absolute()) for input in self.project.inputs()
60-
]
67+
self.manifest_resources = resolve.get_manifest_resources(self.project)
6168

6269
def get_packages_from_manifest(self):
6370
"""Get packages data from manifest files."""
6471
self.resolved_packages = []
6572

66-
for input_location in self.input_locations:
67-
packages = resolve.resolve_packages(input_location)
68-
if not packages:
69-
raise Exception(f"No packages could be resolved for {input_location}")
70-
self.resolved_packages.extend(packages)
73+
if not self.manifest_resources.exists():
74+
self.project.add_warning(
75+
description="No manifests found for resolving packages",
76+
model="get_packages_from_manifest",
77+
)
78+
return
79+
80+
for resource in self.manifest_resources:
81+
if packages := resolve.resolve_packages(resource.location):
82+
self.resolved_packages.extend(packages)
83+
else:
84+
self.project.add_error(
85+
description="No packages could be resolved for",
86+
model="get_packages_from_manifest",
87+
details={"path": resource.path},
88+
)
7189

7290
def create_resolved_packages(self):
7391
"""Create the resolved packages and their dependencies in the database."""

scanpipe/pipes/resolve.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535

3636
from scanpipe.models import DiscoveredPackage
3737
from scanpipe.pipes import cyclonedx
38+
from scanpipe.pipes import flag
3839
from scanpipe.pipes import spdx
3940

4041
"""
@@ -45,8 +46,10 @@
4546
def resolve_packages(input_location):
4647
"""Resolve the packages from manifest file."""
4748
default_package_type = get_default_package_type(input_location)
49+
# we only try to resolve packages if file at input_location is
50+
# a package manifest, and ignore for other files
4851
if not default_package_type:
49-
raise Exception(f"No package type found for {input_location}")
52+
return
5053

5154
# The ScanCode.io resolvers take precedence over the ScanCode-toolkit ones.
5255
resolver = resolver_registry.get(default_package_type)
@@ -59,6 +62,16 @@ def resolve_packages(input_location):
5962
return resolved_packages
6063

6164

65+
def get_manifest_resources(project):
66+
"""Get all resources in the codebase which are package manifests."""
67+
for resource in project.codebaseresources.no_status():
68+
manifest_type = get_default_package_type(input_location=resource.location)
69+
if manifest_type:
70+
resource.update(status=flag.APPLICATION_PACKAGE)
71+
72+
return project.codebaseresources.filter(status=flag.APPLICATION_PACKAGE)
73+
74+
6275
def resolve_pypi_packages(input_location):
6376
"""Resolve the PyPI packages from the `input_location` requirements file."""
6477
python_version = f"{sys.version_info.major}{sys.version_info.minor}"
Binary file not shown.

scanpipe/tests/test_pipelines.py

+36-7
Original file line numberDiff line numberDiff line change
@@ -843,9 +843,43 @@ def test_scanpipe_inspect_manifest_pipeline_integration(self):
843843
pipeline = run.make_pipeline_instance()
844844

845845
project1.move_input_from(tempfile.mkstemp()[1])
846+
pipeline.execute()
847+
self.assertEqual(1, project1.projectmessages.count())
848+
message = project1.projectmessages.get()
849+
self.assertEqual("get_packages_from_manifest", message.model)
850+
expected = "No manifests found for resolving packages"
851+
self.assertIn(expected, message.description)
852+
853+
def test_scanpipe_inspect_manifest_pipeline_integration_empty_manifest(self):
854+
pipeline_name = "inspect_packages"
855+
project1 = Project.objects.create(name="Analysis")
856+
857+
run = project1.add_pipeline(pipeline_name)
858+
pipeline = run.make_pipeline_instance()
859+
860+
project1.move_input_from(tempfile.mkstemp(suffix="requirements.txt")[1])
861+
pipeline.execute()
862+
self.assertEqual(1, project1.projectmessages.count())
863+
message = project1.projectmessages.get()
864+
self.assertEqual("get_packages_from_manifest", message.model)
865+
expected = "No packages could be resolved for"
866+
self.assertIn(expected, message.description)
867+
868+
def test_scanpipe_inspect_manifest_pipeline_integration_misc(self):
869+
pipeline_name = "inspect_packages"
870+
project1 = Project.objects.create(name="Analysis")
871+
872+
input_location = (
873+
self.data_location / "manifests" / "python-inspector-0.10.0.zip"
874+
)
875+
project1.copy_input_from(input_location)
876+
877+
run = project1.add_pipeline(pipeline_name)
878+
pipeline = run.make_pipeline_instance()
879+
846880
exitcode, out = pipeline.execute()
847-
self.assertEqual(1, exitcode, msg=out)
848-
self.assertIn("No package type found for", out)
881+
self.assertEqual(0, exitcode, msg=out)
882+
self.assertEqual(26, project1.discoveredpackages.count())
849883

850884
@mock.patch("scanpipe.pipes.resolve.resolve_dependencies")
851885
def test_scanpipe_inspect_manifest_pipeline_pypi_integration(
@@ -857,12 +891,7 @@ def test_scanpipe_inspect_manifest_pipeline_pypi_integration(
857891
run = project1.add_pipeline(pipeline_name)
858892
pipeline = run.make_pipeline_instance()
859893

860-
resolve_dependencies.return_value = mock.Mock(packages=[])
861894
project1.move_input_from(tempfile.mkstemp(suffix="requirements.txt")[1])
862-
exitcode, out = pipeline.execute()
863-
self.assertEqual(1, exitcode, msg=out)
864-
self.assertIn("No packages could be resolved", out)
865-
866895
resolve_dependencies.return_value = mock.Mock(packages=[package_data1])
867896
exitcode, out = pipeline.execute()
868897
self.assertEqual(0, exitcode, msg=out)

0 commit comments

Comments
 (0)