From d68c533c8eb445563f6e3b7e813b2baa4a68b1d9 Mon Sep 17 00:00:00 2001 From: Rose Judge Date: Tue, 3 Sep 2019 16:03:52 -0700 Subject: [PATCH] spdx: Handle reporting for empty license metadata Currently, if no license metadata is found (i.e. debian-based images) Tern does not generate valid SPDX. An empty license field still reports as "LicenseRef-". According to the 2.1 spec, if information about the license is unknown, the value should be NOASSERTION. This commit adds a few checks in tern/formats/spdx/spdxtagvalue/generator.py to make sure that a license value exists before trying to report the license information. It also moves the get_package_id functionality originally in tern/classes/package.py to a format in tern/formats/spdx/formats.py as package_id is a value only utilized by SPDX format reports. Since the get_package_id functionality was moved out of classes, the test for this function was removed from the test_class_package test file. tern/formats/spdx/spdxtagvalue/generator.py was updated to pull the package_id info from spdx formats.py and has additional manipulation to handle the case when a debian package is reported in the form [epoch:]upstream_version[-debian_revision]. The colon after the epoch needs to be changed to '-' in order to validate the SPDX report. Additionally, this commit wraps the PackageCopyrightText value in in the case that the copyright statement is more than one line per guidelines from the 2.1 spec. Finally, this commit makes a change to the logic inside update_license_list() that gets rid of the dangling license block at the end of the report if no licenses are available from the container image metadata. Resolves #431 Signed-off-by: Rose Judge --- tern/classes/package.py | 6 ------ tern/formats/spdx/formats.py | 2 ++ tern/formats/spdx/spdxtagvalue/generator.py | 24 +++++++++++++++------ tests/test_class_package.py | 3 --- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/tern/classes/package.py b/tern/classes/package.py index a3cc6da9..de497e85 100644 --- a/tern/classes/package.py +++ b/tern/classes/package.py @@ -155,9 +155,3 @@ def is_equal(self, other): if value != other_pkg_dict[key]: return False return True - - def get_package_id(self): - '''This method returns a string of the name and version for a package - represented as "name.version". This method might be helpful when working - with SPDX documents that require a unique package identifier.''' - return "{0}.{1}".format(self.name, self.version) diff --git a/tern/formats/spdx/formats.py b/tern/formats/spdx/formats.py index a25925a0..b5351111 100644 --- a/tern/formats/spdx/formats.py +++ b/tern/formats/spdx/formats.py @@ -9,6 +9,7 @@ # basic strings tag_value = '{tag}: {value}' +block_text = '{message}' # document level strings spdx_version = 'SPDXVersion: SPDX-2.1' @@ -25,6 +26,7 @@ # Package level strings package_comment = 'PackageComment: {comment}' +package_id = '{name}-{ver}' # Relationship strings contains = 'Relationship: {outer} CONTAINS {inner}' diff --git a/tern/formats/spdx/spdxtagvalue/generator.py b/tern/formats/spdx/spdxtagvalue/generator.py index b5fd3787..e6684b73 100755 --- a/tern/formats/spdx/spdxtagvalue/generator.py +++ b/tern/formats/spdx/spdxtagvalue/generator.py @@ -27,7 +27,10 @@ def get_document_namespace(image_obj): def get_package_spdxref(package_obj): '''Given the package object, return an SPDX reference ID''' - return 'SPDXRef-{}'.format(package_obj.get_package_id()) + return 'SPDXRef-{}'.format( + spdx_formats.package_id.format( + name=package_obj.name, + ver=package_obj.version).replace(':', '-', 1)) def get_layer_spdxref(layer_obj): @@ -130,7 +133,7 @@ def update_license_list(license_list, license_string): string is not in the list, add it''' licenses = get_package_licenses(license_string) for l in licenses: - if l not in license_list: + if l and l not in license_list: license_list.append(l) @@ -154,9 +157,9 @@ def get_license_block(license_list): block = '' for l in license_list: block = block + spdx_formats.license_id.format( - license_ref=get_license_ref(l)) + '\n' + license_ref=get_license_ref(l) if l else 'NOASSERTION') + '\n' block = block + spdx_formats.extracted_text.format( - orig_license=l) + '\n\n' + orig_license=l if l else 'NOASSERTION') + '\n\n' return block @@ -279,9 +282,16 @@ def generate(self, image_obj_list): for package_obj in layer_obj.packages: package_dict = package_obj.to_dict(template) # update the PackageLicenseDeclared with a LicenseRef string - if 'PackageLicenseDeclared' in package_dict.keys(): - package_dict['PackageLicenseDeclared'] = format_license( - package_obj.pkg_license) + # only if the license data exists + if ('PackageLicenseDeclared' in package_dict.keys() and + package_obj.pkg_license): + package_dict['PackageLicenseDeclared'] = \ + format_license(package_obj.pkg_license) + if ('PackageCopyrightText' in package_dict.keys() and + package_obj.copyright): + package_dict['PackageCopyrightText'] = \ + spdx_formats.block_text.format( + message=package_obj.copyright) # collect all the individual licenses update_license_list(licenses_found, package_obj.pkg_license) report = report + get_main_block( diff --git a/tests/test_class_package.py b/tests/test_class_package.py index 314e455f..f3aa5d19 100644 --- a/tests/test_class_package.py +++ b/tests/test_class_package.py @@ -121,9 +121,6 @@ def testFill(self): self.assertEqual(p.origins.origins[0].notices[2].message, "No metadata for key: download_url") - def testGetPackageId(self): - self.assertEqual(self.p1.get_package_id(), 'p1.1.0') - if __name__ == '__main__': unittest.main()