Repository: vmware/tern
Pull request #234 (2019-04-17 14:43:15)

Groundwork for custom templating - 2
This is work towards enabling custom templates as described in #180
This covers classes Image and DockerImage.

- Changes to classes's to_dict methods to take a Template object
- Tests for the to_dict method with test template objects created
in test fixtures.


Patch:
diff --git a/tern/classes/docker_image.py b/tern/classes/docker_image.py
index 8846273..e884ecc 100644
--- a/tern/classes/docker_image.py
+++ b/tern/classes/docker_image.py
@@ -51,12 +51,11 @@ class DockerImage(Image):
     def history(self):
         return self.__history
 
-    def to_dict(self):
-        super_dict = super().to_dict()
-        super_dict.update({'repotag': self.repotag})
-        super_dict.update({'repotags': self.repotags})
-        super_dict.update({'history': self.history})
-        return super_dict
+    def to_dict(self, template=None):
+        '''Return a dictionary representation of the Docker image'''
+        # this should take care of 'origins' and 'layers'
+        di_dict = super().to_dict()
+        return di_dict
 
     def get_image_option(self):
         '''Check to see which value was used to init the image object
diff --git a/tern/classes/image.py b/tern/classes/image.py
index 02acb02..8028009 100644
--- a/tern/classes/image.py
+++ b/tern/classes/image.py
@@ -4,13 +4,15 @@
 #
 
 from tern.classes.origins import Origins
+from tern.utils.general import prop_names
 
 
 class Image:
     '''A representation of the image to be analyzed
     attributes:
-        image_id: this is a unique identifier for the image - for OCI spec this could
-        be the digest. For now this is the sha256sum of the config.json
+        image_id: this is a unique identifier for the image - for OCI spec
+        this could be the digest. For now this is the sha256sum of the
+        config.json
         manifest: the json object representing the image manifest
         config: the image config metadata
         layers: list of layer objects in the image
@@ -108,14 +110,28 @@ class Image:
         Inherit from this class and override this method'''
         pass
 
-    def to_dict(self):
+    def to_dict(self, template=None):
         '''Return a dictionary representation of the image'''
-        d = {'image_id': self.image_id,
-             'name': self.name,
-             'tag': self.tag,
-             'manifest': self.manifest,
-             'config': self.config,
-             'layers': [layer.to_dict() for layer in self.layers],
-             'notes': self.origins.to_dict()
-             }
-        return d
+        # send template to layer's to_dict method
+        layer_list = [layer.to_dict(template) for layer in self.layers]
+        image_dict = {}
+        if template:
+            # use the template mapping for the key name
+            for key, prop in prop_names(self):
+                if prop in template.image().keys():
+                    image_dict.update(
+                        {template.image()[prop]: self.__dict__[key]})
+            # update 'origins' and 'layers' values if mapping exists
+            if 'origins' in template.image().keys():
+                image_dict.update(
+                    {template.image()['origins']: self.origins.to_dict()})
+            if 'layers' in template.image().keys():
+                image_dict.update({template.image()['layers']: layer_list})
+        else:
+            # use the property name
+            for key, prop in prop_names(self):
+                image_dict.update({prop: self.__dict__[key]})
+            # update 'origins' and 'layers' lists
+            image_dict.update({'layers': layer_list})
+            image_dict.update({'origins': self.origins.to_dict()})
+        return image_dict
diff --git a/tern/classes/image_layer.py b/tern/classes/image_layer.py
index a7c9366..37801a0 100644
--- a/tern/classes/image_layer.py
+++ b/tern/classes/image_layer.py
@@ -109,17 +109,25 @@ class ImageLayer:
         return success
 
     def to_dict(self, template=None):
+        '''Return a dictionary representation of the image layer object'''
         layer_dict = {}
-        pkg_list = []
         # for packages call each package object's to_dict method
-        for pkg in self.__packages:
-            pkg_list.append(pkg.to_dict(template))
+        pkg_list = [pkg.to_dict(template) for pkg in self.packages]
         if template:
             # use the template mapping for key names
             for key, prop in prop_names(self):
                 if prop in template.image_layer().keys():
                     layer_dict.update(
                         {template.image_layer()[prop]: self.__dict__[key]})
+            # update the 'origins' if it exists in the mapping
+            if 'origins' in template.image_layer().keys():
+                layer_dict.update(
+                    {template.image_layer()['origins']: self.origins.to_dict(
+                    )})
+            # update the 'packages' if it exists in the mapping
+            if 'packages' in template.image_layer().keys():
+                layer_dict.update(
+                    {template.image_layer()['packages']: pkg_list})
         else:
             # directly use property names
             for key, prop in prop_names(self):
@@ -128,14 +136,6 @@ class ImageLayer:
             layer_dict.update({'packages': pkg_list})
             # take care of the 'origins' property
             layer_dict.update({'origins': self.origins.to_dict()})
-        # update the 'origins' if it exists in the mapping
-        if template and 'origins' in template.image_layer().keys():
-            layer_dict.update(
-                {template.image_layer()['origins']: self.origins.to_dict()})
-        # update the 'packages' if it exists in the mapping
-        if template and 'packages' in template.image_layer().keys():
-            layer_dict.update(
-                {template.image_layer()['packages']: pkg_list})
         return layer_dict
 
     def get_package_names(self):
diff --git a/tern/classes/package.py b/tern/classes/package.py
index 468e85f..f59a024 100644
--- a/tern/classes/package.py
+++ b/tern/classes/package.py
@@ -71,15 +71,15 @@ class Package:
                 if prop in template.package().keys():
                     pkg_dict.update(
                         {template.package()[prop]: self.__dict__[key]})
+            # update the 'origins' part if it exists in the mapping
+            if 'origins' in template.package().keys():
+                pkg_dict.update(
+                    {template.package()['origins']: self.origins.to_dict()})
         else:
             # don't map, just use the property name as the key
             for key, prop in prop_names(self):
                 pkg_dict.update({prop: self.__dict__[key]})
             pkg_dict.update({'origins': self.origins.to_dict()})
-        # update the 'origins' part if it exists in the mapping
-        if template and 'origins' in template.package().keys():
-            pkg_dict.update(
-                {template.package()['origins']: self.origins.to_dict()})
         return pkg_dict
 
     def fill(self, package_dict):


Test patch:
diff --git a/tests/test_class_image.py b/tests/test_class_image.py
index 6ef72c6..3b42110 100644
--- a/tests/test_class_image.py
+++ b/tests/test_class_image.py
@@ -7,25 +7,69 @@ import unittest
 
 from tern.classes.image import Image
 from tern.classes.origins import Origins
+from test_fixtures import TestImage
+from test_fixtures import TestTemplate1
+from test_fixtures import TestTemplate2
 
 
 class TestClassImage(unittest.TestCase):
 
     def setUp(self):
         '''Test a generic Image class'''
-        self.image = Image('1234abcd')
+        self.image1 = Image('1234abcd')
+        self.image2 = TestImage('5678efgh')
 
     def tearDown(self):
-        del self.image
+        del self.image1
+        del self.image2
 
     def testInstance(self):
-        self.assertEqual(self.image.image_id, '1234abcd')
-        self.assertFalse(self.image.name)
-        self.assertFalse(self.image.manifest)
-        self.assertFalse(self.image.tag)
-        self.assertFalse(self.image.config)
-        self.assertFalse(self.image.layers)
-        self.assertIsInstance(self.image.origins, Origins)
+        self.assertEqual(self.image1.image_id, '1234abcd')
+        self.assertFalse(self.image1.name)
+        self.assertFalse(self.image1.manifest)
+        self.assertFalse(self.image1.tag)
+        self.assertFalse(self.image1.config)
+        self.assertFalse(self.image1.layers)
+        self.assertIsInstance(self.image1.origins, Origins)
+
+    def testLoadImage(self):
+        self.assertEqual(self.image2.image_id, '5678efgh')
+        self.assertFalse(self.image2.layers)
+        self.image2.load_image()
+        self.assertEqual(len(self.image2.layers), 1)
+        self.assertEqual(len(self.image2.layers[0].packages), 2)
+
+    def testGetLayerDiffIds(self):
+        self.image2.load_image()
+        self.assertEqual(self.image2.get_layer_diff_ids(), ['123abc'])
+
+    def testGetLayerObject(self):
+        self.image2.load_image()
+        self.assertEqual(
+             self.image2.get_layer_object('123abc'), self.image2.layers[0])
+
+    def testToDict(self):
+        self.image2.load_image()
+        a_dict = self.image2.to_dict()
+        self.assertEqual(a_dict['image_id'], '5678efgh')
+        self.assertEqual(len(a_dict['layers']), 1)
+        self.assertEqual(len(a_dict['layers'][0]['packages']), 2)
+
+    def testToDictTemplate(self):
+        self.image2.load_image()
+        template1 = TestTemplate1()
+        template2 = TestTemplate2()
+        dict1 = self.image2.to_dict(template1)
+        print(dict1)
+        dict2 = self.image2.to_dict(template2)
+        print(dict2)
+        self.assertEqual(len(dict1.keys()), 2)
+        self.assertEqual(dict1['image.id'], '5678efgh')
+        self.assertEqual(len(dict1['image.layers']), 1)
+        self.assertEqual(len(dict2.keys()), 3)
+        self.assertFalse(dict2['notes'])
+        self.assertFalse(dict2['image.layers'][0]['notes'])
+        self.assertEqual(len(dict2['image.layers'][0]['layer.packages']), 2)
 
 
 if __name__ == '__main__':
diff --git a/tests/test_fixtures.py b/tests/test_fixtures.py
index c7b5e44..e25f54f 100644
--- a/tests/test_fixtures.py
+++ b/tests/test_fixtures.py
@@ -36,7 +36,7 @@ class TestTemplate1(Template):
                 'packages': 'layer.packages'}
 
     def image(self):
-        return {'id': 'image.id',
+        return {'image_id': 'image.id',
                 'layers': 'image.layers'}
 
 
@@ -60,7 +60,7 @@ class TestTemplate2(Template):
         return mapping
 
     def image(self):
-        mapping = {'id': 'image.id',
+        mapping = {'image_id': 'image.id',
                    'layers': 'image.layers'}
         # we update the mapping with another defined mapping
         mapping.update(self.origins())