Repository: vmware/tern
Pull request #462 (2019-09-25 22:49:24)

docs: Update references in navigating-the-code.md
There have been some recent PRs that have re-factored where certain
files live. This commit updates docs/navigating-the-code.md to reflect
the changes that have been made specifically with regards to the plugin
architecture for Tern's reporting formats.

Signed-off-by: Rose Judge <rjudge@vmware.com>


Patch:
diff --git a/docs/navigating-the-code.md b/docs/navigating-the-code.md
index 90d7514..a8ac7f8 100644
--- a/docs/navigating-the-code.md
+++ b/docs/navigating-the-code.md
@@ -16,12 +16,23 @@ Here is a layout of the directory structure:
       origins.py
       package.py
       template.py
-    ▾ templates/
-        spdx.py
   ▾ command_lib/ <-- These are the bash commands that get run to collect information about the container image
       base.yml <-- System wide scripts for package managers and such
       snippets.yml <-- scripts for one off commands
       command_lib.py <-- Command Library modules
+  ▾ formats/ <-- This is the reporting template plugin library. Each subdirectory is a module that can be dynamically loaded at runtime based on the users report selection
+      generator.py <-- This is the abstract base class for report plugins
+    ▾ default/
+        generator.py
+    ▾ json/
+        generator.py
+    ▾ spdx/
+        formats.py
+        spdx.py
+      ▾ spdxtagvalue/
+           generator.py
+    ▾ yaml/
+         generator.py
   ▾ helpers/
       common.py <-- Common modules used at a high level throughout the code
       docker.py <-- modules specific to Docker
@@ -30,9 +41,6 @@ Here is a layout of the directory structure:
       errors.py
       formats.py
       report.py <-- Main reporting module
-    ▾ spdxtagvalue/ <-- A Template module
-        formats.py
-        generator.py
   ▾ scripts/debian/ <-- Example script to pull sources for debian based images
     ▾ jessie/
         sources.list
@@ -73,7 +81,9 @@ Scripts used by our CI/CD setup live outside the `tern` folder, in a folder call
 
 ```
 ▾ ci/
+    evaluate_docs.py
     test_commit_message.py
+    test_files_touched.py
 ```
 
 Documentation lives in a folder called `docs`. Sample Dockerfile live in a folder called `samples`.
diff --git a/tern/classes/image_layer.py b/tern/classes/image_layer.py
index e859152..77e759e 100644
--- a/tern/classes/image_layer.py
+++ b/tern/classes/image_layer.py
@@ -29,6 +29,7 @@ class ImageLayer:
         import_str: The string from a build tool (like a Dockerfile) that
         created this layer by importing it from another image
         files_analyzed: whether the files in this layer are analyzed or not
+        analyzed_output: the result of the file analysis
     methods:
         add_package: adds a package to the layer
         remove_package: removes a package from the layer
@@ -47,6 +48,7 @@ class ImageLayer:
         self.__pkg_format = ''
         self.__os_guess = ''
         self.__files_analyzed = False
+        self.__analyzed_output = ''
 
     @property
     def diff_id(self):
@@ -108,6 +110,17 @@ class ImageLayer:
     def os_guess(self, os_guess):
         self.__os_guess = os_guess
 
+    @property
+    def analyzed_output(self):
+        return self.__analyzed_output
+
+    @analyzed_output.setter
+    def analyzed_output(self, analyzed_output):
+        if isinstance(analyzed_output, str):
+            self.__analyzed_output = analyzed_output
+        else:
+            raise ValueError('analyzed_output should be a string')
+    
     @property
     def files_analyzed(self):
         return self.__files_analyzed
@@ -118,7 +131,7 @@ class ImageLayer:
             self.__files_analyzed = x
         else:
             raise ValueError('files_analyzed should be boolean')
-    
+
     def add_package(self, package):
         if isinstance(package, Package):
             if package.name not in self.get_package_names():
diff --git a/tern/formats/default/generator.py b/tern/formats/default/generator.py
index 4f771e7..3c32ae9 100644
--- a/tern/formats/default/generator.py
+++ b/tern/formats/default/generator.py
@@ -23,8 +23,7 @@ def print_full_report(image):
     '''Given an image, go through the Origins object and collect all the
     notices for the image, layers and packages'''
     notes = ''
-    pkg_list = []
-    license_list = []
+    full_license_list = []
     for image_origin in image.origins.origins:
         notes = notes + content.print_notices(image_origin, '', '\t')
     for layer in image.layers:
@@ -34,19 +33,27 @@ def print_full_report(image):
             for layer_origin in layer.origins.origins:
                 notes = notes + content.print_notices(layer_origin,
                                                       '\t', '\t\t')
+            layer_pkg_list = []
+            layer_license_list = []
             for package in layer.packages:
                 pkg = package.name + "-" + package.version
-                if pkg not in pkg_list and pkg:
-                    pkg_list.append(pkg)
-                if package.pkg_license not in license_list and \
+                if pkg not in layer_pkg_list and pkg:
+                    layer_pkg_list.append(pkg)
+                if package.pkg_license not in layer_license_list and \
                         package.pkg_license:
-                    license_list.append(package.pkg_license)
+                    layer_license_list.append(package.pkg_license)
+                    if package.pkg_license not in full_license_list:
+                        full_license_list.append(package.pkg_license)
+            # Collect packages + licenses in the layer
+            notes = notes + formats.layer_packages_list.format(
+                list=", ".join(layer_pkg_list) if layer_pkg_list else 'None')
+            notes = notes + formats.layer_licenses_list.format(list=", ".join(
+                layer_license_list) if layer_license_list else 'None')
             notes = notes + formats.package_demarkation
-    packages = formats.packages_list.format(list=", ".join(pkg_list) if
-                                            pkg_list else 'None')
-    licenses = formats.licenses_list.format(list=", ".join(license_list) if
-                                            license_list else 'None')
-    return notes + packages + formats.package_demarkation + licenses
+    # Collect the full list of licenses from all the layers
+    licenses = formats.full_licenses_list.format(list=", ".join(
+        full_license_list) if full_license_list else 'None')
+    return notes + licenses
 
 
 class Default(generator.Generate):
diff --git a/tern/report/formats.py b/tern/report/formats.py
index c578bc5..0d03cbb 100644
--- a/tern/report/formats.py
+++ b/tern/report/formats.py
@@ -43,8 +43,11 @@ package_version = '''Version: {package_version}\n'''
 package_url = '''Project URL: {package_url}\n'''
 package_license = '''License: {package_license}\n'''
 package_copyright = '''Copyright Text: {package_copyright}\n'''
-packages_list = '''Packages Found:  {list}\n'''
-licenses_list = '''Licenses Found:  {list}\n'''
+layer_packages_list = '''\tPackages found in Layer:  {list}\n'''
+layer_licenses_list = '''\tLicenses found in Layer:  {list}\n'''
+full_licenses_list = '''###########################################\n'''\
+    '''# Summary of licenses found in Container: #\n'''\
+    '''###########################################\n{list}\n'''
 
 # notes
 package_notes = '''Errors: {package_info_retrieval_errors}\n''' \


Test patch:
diff --git a/tests/test_class_image_layer.py b/tests/test_class_image_layer.py
index 259ffdd..8946259 100644
--- a/tests/test_class_image_layer.py
+++ b/tests/test_class_image_layer.py
@@ -39,6 +39,11 @@ class TestClassImageLayer(unittest.TestCase):
         self.assertTrue(self.layer.files_analyzed)
         self.assertRaises(ValueError, setattr, self.layer,
                           'files_analyzed', 'some string')
+        self.assertEqual("", self.layer.analyzed_output)
+        self.layer.analyzed_output = 'some string'
+        self.assertEqual(self.layer.analyzed_output, 'some string')
+        self.assertRaises(ValueError, setattr, self.layer,
+                          'analyzed_output', 123)
 
     def testAddPackage(self):
         err = "Object type String, should be Package"