Skip to content

Commit e58f279

Browse files
committed
Try to improve performance by adding pagination
Fix filename for export files Add multiple parameterizes for create_sub_path test . Add new format for exporting vulnerablecode-data Add a test Fix export test with yaml format Change the export format from json to yaml Add test for export command Add test for write_vuln_data function Edit export.py , Fix missing attribute in vuln_data Export vulnerablecode-data Add new format for exporting vulnerablecode-data Add a test Fix export test with yaml format Change the export format from json to yaml Add test for export command Add test for write_vuln_data function Edit export.py , Fix missing attribute in vuln_data Export vulnerablecode-data Add new format for exporting vulnerablecode-data Add a test Fix export test with yaml format Change the export format from json to yaml Add test for export command Add test for write_vuln_data function Edit export.py , Fix missing attribute in vuln_data Export vulnerablecode-data Signed-off-by: ziadhany <ziadhany2016@gmail.com>
1 parent 582a1fb commit e58f279

2 files changed

Lines changed: 251 additions & 0 deletions

File tree

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# VulnerableCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/nexB/vulnerablecode for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
from pathlib import Path
10+
11+
import saneyaml
12+
from django.core.management.base import BaseCommand
13+
from django.core.management.base import CommandError
14+
from packageurl import PackageURL
15+
16+
from vulnerabilities.models import Package
17+
18+
19+
class Command(BaseCommand):
20+
help = "export vulnerablecode data"
21+
22+
def add_arguments(self, parser):
23+
parser.add_argument("path")
24+
25+
def handle(self, *args, **options):
26+
if options["path"]:
27+
git_path = Path(options["path"])
28+
if not git_path.is_dir():
29+
raise CommandError("Please enter a valid path")
30+
31+
self.export_data(git_path)
32+
33+
self.stdout.write(self.style.SUCCESS("Successfully exported vulnerablecode data"))
34+
35+
def export_data(self, git_path):
36+
"""
37+
export vulnerablecode data
38+
by run `python manage.py export /path/vulnerablecode-data`
39+
"""
40+
self.stdout.write(f"Exporting vulnerablecode data")
41+
42+
for purl in Package.objects.all().paginated():
43+
package_dir = create_sub_paths(git_path, purl.type, purl.namespace, purl.name)
44+
pck_filepath = package_dir.joinpath(f"{purl.type}-{purl.name}.yaml")
45+
write_package_data(pck_filepath, purl)
46+
47+
for vul in purl.vulnerabilities.all():
48+
vul_filepath = package_dir.joinpath(f"{vul.vulnerability_id}.yaml")
49+
write_vul_data(vul_filepath, vul)
50+
51+
52+
def write_vul_data(filepath, vul):
53+
"""
54+
write the vulnerability data in a file with a path like this
55+
`path/ecosystem/package/VCID-XXXX-XXXX-XXXX` and create the directories if it doesn't exist
56+
"""
57+
58+
vul_data = saneyaml.dump(
59+
{
60+
"vulnerability_id": vul.vulnerability_id,
61+
"aliases": [alias.alias for alias in vul.get_aliases],
62+
"summary": vul.summary,
63+
"severities": [severity for severity in vul.severities.values()],
64+
"references": [ref for ref in vul.references.values()],
65+
"weaknesses": [
66+
"CWE-" + str(weakness["cwe_id"]) for weakness in vul.weaknesses.values()
67+
],
68+
}
69+
)
70+
71+
with open(filepath, encoding="utf-8", mode="w") as f:
72+
f.write(vul_data)
73+
74+
75+
def write_package_data(filepath, purl):
76+
"""
77+
write the pacakge data in a file with a path like this
78+
`path/ecosystem/package/package_type-package_name` and create the directories if it doesn't exist
79+
and if the pacakge file doesn't exist we create the new header
80+
"""
81+
purl_object = PackageURL.from_string(purl.package_url)
82+
purl_without_version = PackageURL(
83+
type=purl_object.type,
84+
namespace=purl_object.namespace,
85+
name=purl_object.name,
86+
)
87+
88+
package_data_with_header = saneyaml.dump(
89+
{
90+
"pacakge": str(purl_without_version),
91+
"versions": [
92+
{
93+
"purl": str(purl_object),
94+
"affected_by_vulnerabilities": [
95+
vuln.vulnerability_id for vuln in purl.affected_by
96+
],
97+
"fixing_vulnerabilities": [vuln.vulnerability_id for vuln in purl.fixing],
98+
}
99+
],
100+
}
101+
)
102+
103+
package_data = {
104+
"purl": str(purl_object),
105+
"affected_by_vulnerabilities": [vuln.vulnerability_id for vuln in purl.affected_by],
106+
"fixing_vulnerabilities": [vuln.vulnerability_id for vuln in purl.fixing],
107+
}
108+
109+
if Path(filepath).is_file():
110+
with open(filepath, "r") as f:
111+
old_yaml = saneyaml.load(f)
112+
old_yaml["versions"].append(package_data)
113+
114+
if old_yaml:
115+
with open(filepath, "w") as f:
116+
f.write(saneyaml.dump(old_yaml))
117+
else:
118+
with open(filepath, encoding="utf-8", mode="w") as f:
119+
f.write(package_data_with_header)
120+
121+
122+
def create_sub_paths(git_path, purl_type, purl_namespace, purl_name):
123+
"""
124+
create the directories if it doesn't exist : `path/purl_type/purl_namespace/purl_name`
125+
"""
126+
ecosystem_dir = git_path.joinpath(purl_type)
127+
if not ecosystem_dir.is_dir():
128+
ecosystem_dir.mkdir()
129+
130+
namespace_dir = ecosystem_dir.joinpath(purl_namespace)
131+
if not namespace_dir.is_dir():
132+
namespace_dir.mkdir()
133+
134+
package_dir = namespace_dir.joinpath(purl_name)
135+
if not package_dir.is_dir():
136+
package_dir.mkdir()
137+
138+
return package_dir
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import os
2+
from io import StringIO
3+
from pathlib import Path
4+
from unittest import TestCase
5+
6+
import pytest
7+
import saneyaml
8+
from django.core.management import call_command
9+
from django.core.management.base import CommandError
10+
11+
from vulnerabilities.management.commands.export import create_sub_paths
12+
from vulnerabilities.management.commands.export import write_package_data
13+
from vulnerabilities.management.commands.export import write_vul_data
14+
from vulnerabilities.models import Package
15+
from vulnerabilities.models import PackageRelatedVulnerability
16+
from vulnerabilities.models import Vulnerability
17+
18+
19+
@pytest.mark.parametrize(
20+
"purl_type,purl_namespace,purl_name",
21+
[
22+
("generic", "", "nginx"),
23+
("github", "package-url", "purl-spec"),
24+
("pypi", "", "django"),
25+
],
26+
)
27+
def test_create_sub_paths(tmp_path, purl_type, purl_namespace, purl_name):
28+
create_sub_paths(tmp_path, purl_type, purl_namespace, purl_name)
29+
30+
ecosystem_dir = os.path.join(tmp_path, purl_type)
31+
assert os.path.isdir(ecosystem_dir)
32+
33+
namespace_dir = os.path.join(ecosystem_dir, purl_namespace)
34+
assert os.path.isdir(namespace_dir)
35+
36+
name_dir = os.path.join(namespace_dir, purl_name)
37+
assert os.path.isdir(name_dir)
38+
39+
40+
@pytest.fixture
41+
def package(db):
42+
return Package.objects.create(
43+
type="generic", namespace="nginx", name="test", version="2", qualifiers={}, subpath=""
44+
)
45+
46+
47+
@pytest.fixture
48+
def vulnerability(db):
49+
return Vulnerability.objects.create(
50+
vulnerability_id="VCID-pst6-b358-aaap",
51+
summary="test-vuln",
52+
)
53+
54+
55+
@pytest.fixture
56+
def package_related_vulnerability(db, package, vulnerability):
57+
PackageRelatedVulnerability.objects.create(
58+
package=package,
59+
vulnerability=vulnerability,
60+
fix=False,
61+
)
62+
return package
63+
64+
65+
@pytest.mark.django_db
66+
def test_write_vul_data(tmp_path, vulnerability):
67+
expected_data = {
68+
"vulnerability_id": "VCID-pst6-b358-aaap",
69+
"aliases": [],
70+
"summary": "test-vuln",
71+
"severities": [],
72+
"references": [],
73+
"weaknesses": [],
74+
}
75+
pck_filepath = os.path.join(tmp_path, "filename")
76+
write_vul_data(pck_filepath, vulnerability)
77+
assert os.path.isfile(pck_filepath)
78+
assert Path(pck_filepath).read_text() == saneyaml.dump(expected_data)
79+
80+
81+
@pytest.mark.django_db
82+
def test_write_package_data(tmp_path, package_related_vulnerability):
83+
expected_data = {
84+
"pacakge": "pkg:generic/nginx/test",
85+
"versions": [
86+
{
87+
"purl": "pkg:generic/nginx/test@2",
88+
"affected_by_vulnerabilities": ["VCID-pst6-b358-aaap"],
89+
"fixing_vulnerabilities": [],
90+
}
91+
],
92+
}
93+
pck_filepath = os.path.join(tmp_path, "filename")
94+
write_package_data(pck_filepath, package_related_vulnerability)
95+
assert os.path.isfile(pck_filepath)
96+
assert Path(pck_filepath).read_text() == saneyaml.dump(expected_data)
97+
98+
99+
class TestExportCommand(TestCase):
100+
def test_missing_path(self):
101+
with pytest.raises(CommandError) as cm:
102+
call_command("export", stdout=StringIO())
103+
104+
err = str(cm)
105+
assert "Error: the following arguments are required: path" in err
106+
107+
def test_bad_path_fail_error(self):
108+
buf = StringIO()
109+
with pytest.raises(CommandError) as cm:
110+
call_command("export", "/bad path", stdout=buf)
111+
112+
err = str(cm)
113+
assert "Please enter a valid path" in err

0 commit comments

Comments
 (0)