Repository: vmware/tern (python)

Issue:
/usr/bin/python3: No module named tern error in vagrant running in windows 10 Personal computer
**No module named tern**

The documentation is insufficient 

I used vagrant up with tern project in my local drive

**To Reproduce**
Steps to reproduce the behavior:
1. I followed the vagrant procedure as mentioned in the **https://github.com/vmware/tern/blob/master/README.md#getting-started-with-vagrant**
2. when I run the "python -m tern -l report -i debian:buster -f output.txt" command 
3. Python command doesn't exist came
4. So I altered the command to python3 in step 2 and ran "python**3** -m tern -l report -i debian:buster -f output.txt" command 
3. threw below error

**Error in terminal**
```
/usr/bin/python3: No module named tern
```

**Expected behavior**
I was expecting the BOM report

**Environment you are running Tern on**
Enter all that apply
- Operating System is Windows 10
- Vagrant file https://github.com/vmware/tern/blob/master/vagrant/Vagrantfile
- Python version (3.6 or higher)- 3.6.8




Pull request:
Fix vagrant documentation and bootstrap file
This commit makes two primary changes that enable users to run Tern
with greater ease. Primarily:

1) Updates the README documentation to instruct users to run Tern
   using the 'python3' command instead of 'python'. The vagrant VM OS,
   Ubuntu LTS, links python2.7 to python2 and python3.6 to python3.
   Tern's vagrant/bootstrap.sh file only installs python3 deps and
   therefore, python/python2 is not present on the system. If you
   follow the current directions in README and try to use the python
   command you will see the following error:

  ` Command 'python' not found, but can be installed with:`

   If you use python3, the python 3.6.8 binary runs properly which
   is what we should be telling users unfamiliar with Tern to use.

2) Updates vagrant/bootstrap.sh to pip3 install '.' instead of only the
   requirements.txt packages. When only the requirements were
   installed, the tern command was not recognized in a fresh vagrant
   VM. Using the old bootstrap file, the below error was seen when
   you followed the README directions and ssh'd into a new VM and tried
   to run Tern with python3:

   `/usr/bin/python3: No module named tern`

   If you change the boostrap file to install `pip3 install .` instead
   of only 'requirements.txt', Tern runs without any additional
   configuration steps in the VM environment, which  makes it easier
   for first time users to run Tern.

Lastly, this commit adds a note to the README documentation instructing
Mac OS and Windows users to only run tern in a vagrant VM. Since
tern/tools/fs_hash.sh utilizes Bash commands, it is only able to
run in a Linux environment. If Tern is run Natively on a Mac/Windows
the Bash command in fs_hash.sh won't work properly and Tern will
return with the warning "Cannot retrieve full image metadata"

Resolves #353

Signed-off-by: Rose Judge <rjudge@vmware.com>

Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md
index 2d5ea6f..e79f816 100644
--- a/.github/ISSUE_TEMPLATE/bug.md
+++ b/.github/ISSUE_TEMPLATE/bug.md
@@ -24,6 +24,7 @@ A clear and concise description of what you expected to happen.
 
 **Environment you are running Tern on**
 Enter all that apply
+- Output of 'tern --version'
 - Operating System (Linux Distro and version or Mac or Windows)
 - Vagrant file
 - Container OS
diff --git a/README.md b/README.md
index d96d4a3..29fd675 100644
--- a/README.md
+++ b/README.md
@@ -76,7 +76,7 @@ What the `docker_run.sh` script does is create the directory `workdir` if not pr
 *WARNING:* privileged Docker containers are not secure. DO NOT run this container in production unless you have secured the node (VM or bare metal machine) that the docker daemon is running on.
 
 ## Getting Started with Vagrant<a name="getting-started-with-vagrant">
-Vagrant is a tool to setup an isolated virtual software development environment. If you are using Windows or Mac OSs, this is a good way to get started.
+Vagrant is a tool to setup an isolated virtual software development environment. If you are using Windows or Mac OSes, this is the best way to get started as Tern does not run natively in a Mac OS or Windows environment at this time.
 
 ### Install
 Follow the instructions on the [VirtualBox](https://www.virtualbox.org/wiki/Downloads) website to download VirtualBox on your OS.
@@ -98,13 +98,13 @@ $ vagrant up
 ```
 
 SSH into the created VM: 
- ```
+```
  $ vagrant ssh
- ```
+```
 
 Run the program:
 ```
-$ python -m tern -l report -i debian:buster -f output.txt
+$ python3 -m tern -l report -i debian:buster -f output.txt
 ```
 
 ## Getting Started on Linux<a name="getting-started-on-linux">
diff --git a/tern/__main__.py b/tern/__main__.py
index 6795222..4a9113e 100755
--- a/tern/__main__.py
+++ b/tern/__main__.py
@@ -13,10 +13,13 @@ import argparse
 import importlib
 import logging
 import os
+import sys
 
 from tern.report import report
 from tern.utils import cache
 from tern.utils import constants
+from tern import Version
+
 
 # global logger
 logger = logging.getLogger(constants.logger_name)
@@ -77,6 +80,7 @@ def do_main(args):
 
 def main():
     parser = argparse.ArgumentParser(
+        formatter_class=argparse.RawTextHelpFormatter,
         prog='Tern',
         description='''
            Tern is a container image component curation tool. Tern retrieves
@@ -95,6 +99,11 @@ def main():
                         "Needed when running from within a container")
     parser.add_argument('-r', '--redo', action='store_true',
                         help="Repopulate the cache for found layers")
+    # sys.version gives more information than we care to print
+    py_ver = sys.version.replace('\n', '').split('[')[0]
+    parser.add_argument('-V', '--version', action='version',
+                        version="%(prog)s {0}\n   python version = {1}".format(
+                            Version, py_ver))
     subparsers = parser.add_subparsers(help='Subcommands')
     # subparser for report
     parser_report = subparsers.add_parser('report',
diff --git a/tern/classes/package.py b/tern/classes/package.py
index 1cce92a..a3cc6da 100644
--- a/tern/classes/package.py
+++ b/tern/classes/package.py
@@ -18,6 +18,7 @@ class Package:
         proj_url: package source url
         download_url: package download url
         origins: a list of NoticeOrigin objects
+        checksum: checksum as package property
 
     methods:
         to_dict: returns a dict representation of the instance
@@ -30,6 +31,7 @@ class Package:
         self.__copyright = ''
         self.__proj_url = ''
         self.__download_url = ''
+        self.__checksum = ''
         self.__origins = Origins()
 
     @property
@@ -80,6 +82,14 @@ class Package:
     def origins(self):
         return self.__origins
 
+    @property
+    def checksum(self):
+        return self.__checksum
+
+    @checksum.setter
+    def checksum(self, checksum):
+        self.__checksum = checksum;
+
     def to_dict(self, template=None):
         '''Return a dictionary version of the Package object
         If given an object which is a subclass of Template then map
diff --git a/vagrant/bootstrap.sh b/vagrant/bootstrap.sh
index 2a2962e..815cbcc 100755
--- a/vagrant/bootstrap.sh
+++ b/vagrant/bootstrap.sh
@@ -25,4 +25,4 @@ sudo usermod -a -G docker vagrant
 
 # Install dependencies
 cd /tern
-pip3 install -r requirements.txt
+pip3 install .


Test patch:
diff --git a/tests/test_class_package.py b/tests/test_class_package.py
index b491030..314e455 100644
--- a/tests/test_class_package.py
+++ b/tests/test_class_package.py
@@ -19,6 +19,7 @@ class TestClassPackage(unittest.TestCase):
         self.p1.copyright = 'All Rights Reserved'
         self.p1.proj_url = 'github.com'
         self.p1.download_url = 'https://github.com'
+        self.p1.checksum = '123abc456'
 
         self.p2 = Package('p2')
 
@@ -33,6 +34,7 @@ class TestClassPackage(unittest.TestCase):
         self.assertFalse(self.p2.pkg_license)
         self.assertFalse(self.p2.copyright)
         self.assertFalse(self.p2.download_url)
+        self.assertFalse(self.p2.checksum)
 
     def testSetters(self):
         self.assertRaises(AttributeError, setattr, self.p2, 'name', 'y')
@@ -46,6 +48,8 @@ class TestClassPackage(unittest.TestCase):
         self.assertEqual(self.p2.proj_url, 'github.com')
         self.p2.download_url = 'https://github.com'
         self.assertEqual(self.p2.download_url, 'https://github.com')
+        self.p2.checksum = '123abc456'
+        self.assertEqual(self.p2.checksum, '123abc456')
 
     def testGetters(self):
         self.assertEqual(self.p1.name, 'p1')
@@ -54,6 +58,7 @@ class TestClassPackage(unittest.TestCase):
         self.assertEqual(self.p1.copyright, 'All Rights Reserved')
         self.assertEqual(self.p1.proj_url, 'github.com')
         self.assertEqual(self.p1.download_url, 'https://github.com')
+        self.assertEqual(self.p1.checksum, '123abc456')
 
     def testToDict(self):
         a_dict = self.p1.to_dict()
@@ -64,6 +69,7 @@ class TestClassPackage(unittest.TestCase):
         self.assertEqual(a_dict['proj_url'], 'github.com')
         self.assertEqual(a_dict['download_url'], 'https://github.com')
         self.assertFalse(a_dict['origins'])
+        self.assertEqual(a_dict['checksum'], '123abc456')
 
     def testToDictTemplate(self):
         template1 = TestTemplate1()
@@ -82,9 +88,11 @@ class TestClassPackage(unittest.TestCase):
         p.pkg_license = 'Testpkg_license'
         p.version = '1.0'
         p.proj_url = 'TestUrl'
+        p.checksum = 'abcdef'
         self.p2.pkg_license = 'Testpkg_license'
         self.p2.version = '2.0'
         self.p2.proj_url = 'TestUrl'
+        self.p2.checksum = 'abcdef'
         self.assertFalse(self.p2.is_equal(p))
         p.version = '2.0'
         self.assertTrue(self.p2.is_equal(p))
@@ -92,12 +100,14 @@ class TestClassPackage(unittest.TestCase):
     def testFill(self):
         p_dict = {'name': 'p1',
                   'version': '1.0',
-                  'pkg_license': 'Apache 2.0'}
+                  'pkg_license': 'Apache 2.0',
+                  'checksum': 'abcxyz'}
         p = Package('p1')
         p.fill(p_dict)
         self.assertEqual(p.name, 'p1')
         self.assertEqual(p.version, '1.0')
         self.assertEqual(p.pkg_license, 'Apache 2.0')
+        self.assertEqual(p.checksum, 'abcxyz')
         self.assertFalse(p.copyright)
         self.assertFalse(p.proj_url)
         self.assertEqual(len(p.origins.origins), 1)