Skip to content

Commit c4b3508

Browse files
authored
Add purl information to SPDX reports
This commit adds a new function, get_purl() to spdx_common.py which uses the `packageurl` library to generate purl strings for given package objects. The namespace for certain purls is determined using the /etc/os-release file information collected via the pkg_suppliers field in base.yml. This commit then adds purl strings as external references[1] to both SPDX tag value and SPDX json reports. [1]https://spdx.github.io/spdx-spec/v2.3/package-information/#721-external-reference-field Resolves #1206 Signed-off-by: Rose Judge <[email protected]> Signed-off-by: Ivana Atanasova <[email protected]>
2 parents 3624b30 + a5ebbc1 commit c4b3508

File tree

4 files changed

+56
-5
lines changed

4 files changed

+56
-5
lines changed

tern/analyze/default/command_lib/base.yml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ tdnf:
5959
- 'tdnf check-update > /dev/null'
6060
- 'tdnf list installed | cut -f2 -d"." | cut -f1 -d" "'
6161
delimiter: "\n"
62+
pkg_suppliers:
63+
invoke:
64+
1:
65+
container:
66+
- 'tdnf check-update > /dev/null'
67+
- "distro=`/bin/cat /etc/os-release | grep NAME | sed -n '1p' | cut -f 2 -d '=' | cut -d '\"' -f2`"
68+
- 'pkgs=`tdnf list installed | cut -f1 -d"."`'
69+
- "for p in $pkgs; do echo $distro; done"
70+
delimiter: "\n"
6271
files: {}
6372
proj_urls:
6473
invoke:
@@ -180,8 +189,9 @@ apk:
180189
invoke:
181190
1:
182191
container:
192+
- "distro=`/bin/cat /etc/os-release | grep NAME | sed -n '1p' | cut -f 2 -d '=' | cut -d '\"' -f2`"
183193
- "pkgs=`apk info 2>/dev/null`"
184-
- "for p in $pkgs; do echo 'Alpine Linux'; done"
194+
- "for p in $pkgs; do echo $distro; done"
185195
delimiter: "\n"
186196
licenses:
187197
invoke:
@@ -363,7 +373,7 @@ pip:
363373
1:
364374
container:
365375
- "pkgs=`pip list --format=freeze 2> /dev/null | cut -f1 -d'='`"
366-
- "for p in $pkgs; do echo 'PyPI'; done"
376+
- "for p in $pkgs; do echo 'Python Package Index'; done"
367377
delimiter: "\n"
368378
licenses:
369379
invoke:
@@ -413,7 +423,7 @@ pip3:
413423
1:
414424
container:
415425
- "pkgs=`pip3 list --format=freeze 2> /dev/null | cut -f1 -d'='`"
416-
- "for p in $pkgs; do echo 'PyPI'; done"
426+
- "for p in $pkgs; do echo 'Python Package Index'; done"
417427
delimiter: "\n"
418428
licenses:
419429
invoke:

tern/formats/spdx/spdx_common.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from license_expression import get_spdx_licensing
1717
from tern.utils import constants
1818
from tern.formats.spdx.spdxtagvalue import formats as spdx_formats
19+
from packageurl import PackageURL
1920

2021
# global logger
2122
logger = logging.getLogger(constants.logger_name)
@@ -210,3 +211,34 @@ def get_file_comment(filedata):
210211
comment = comment + \
211212
'{}: {}'.format(notice.level, notice.message) + '\n'
212213
return comment
214+
215+
216+
#######################
217+
# Common PURL Helpers #
218+
#######################
219+
220+
purl_types_with_namespaces = [
221+
'deb',
222+
'rpm',
223+
'apk',
224+
'alpm'
225+
]
226+
227+
228+
def get_purl(package_obj):
229+
'''Return a purl string for a given package'''
230+
purl_type = package_obj.pkg_format
231+
purl_namespace = ''
232+
if purl_type in purl_types_with_namespaces and package_obj.pkg_supplier:
233+
# https://github.com/package-url/purl-spec/pull/214
234+
if package_obj.pkg_supplier.split(' ')[0] == "VMware":
235+
purl_namespace = package_obj.pkg_supplier.split(' ')[1].lower()
236+
else:
237+
purl_namespace = package_obj.pkg_supplier.split(' ')[0].lower()
238+
# TODO- this might need adjusting for alpm. Currently can't test on M1
239+
purl = PackageURL(purl_type, purl_namespace, package_obj.name.lower(), package_obj.version,
240+
qualifiers={'arch': package_obj.arch if package_obj.arch else ''})
241+
try:
242+
return purl.to_string()
243+
except ValueError:
244+
return ''

tern/formats/spdx/spdxjson/package_helpers.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,14 @@ def get_package_dict(package, template):
7474
mapping['PackageLicenseDeclared']),
7575
'copyrightText': mapping['PackageCopyrightText'] if
7676
mapping['PackageCopyrightText'] else 'NONE',
77-
'comment': get_package_comment(package)
7877
}
79-
78+
# Only add package PURL if it exists
79+
if spdx_common.get_purl(package):
80+
package_dict['externalRefs'] = [{'referenceCategory': 'PACKAGE-MANAGER',
81+
'referenceLocator': spdx_common.get_purl(package),
82+
'referenceType': 'purl'}]
83+
# Put package comment after any potential externalRefs
84+
package_dict['comment'] = get_package_comment(package)
8085
return package_dict
8186

8287

tern/formats/spdx/spdxtagvalue/package_helpers.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ def get_package_block(package_obj, template):
115115
message=mapping['PackageCopyrightText']) + '\n'
116116
else:
117117
block += 'PackageCopyrightText: NONE\n'
118+
# Package URL
119+
if spdx_common.get_purl(package_obj):
120+
block += 'ExternalRef: PACKAGE-MANAGER purl {}\n'.format(
121+
spdx_common.get_purl(package_obj))
118122
# Package Comments
119123
block += get_package_comment(package_obj)
120124
return block

0 commit comments

Comments
 (0)