Repository: tern-tools/tern

Issue:
Report RUN command statements that are non-deterministic like if and case statements
**Describe the Feature**
Currently, the report notices just says that if, case and for statements are unrecognizable. It would be nice to add notices for non-deterministic branching statements like if and case statements as you could only know the status of the branch at build time.

**Implementation Changes**
The new shell script parser produces a dictionary that will identify if and case statements. We could just look at this object and extract the if and case statements to create notices for them.


Discussion:

Patch:
diff --git a/tern/analyze/common.py b/tern/analyze/common.py
index df4889a..a9de792 100644
--- a/tern/analyze/common.py
+++ b/tern/analyze/common.py
@@ -31,9 +31,11 @@ logger = logging.getLogger(constants.logger_name)
 
 
 def get_shell_commands(shell_command_line):
-    '''Given a shell command line, get a list of Command objects'''
+    '''Given a shell command line, get a list of Command objects and report on
+    branch statements'''
     statements = general.split_command(shell_command_line)
     command_list = []
+    branch_report = ''
     # traverse the statements, pick out the loop and commands.
     for stat in statements:
         if 'command' in stat:
@@ -43,7 +45,13 @@ def get_shell_commands(shell_command_line):
             for st in loop_stat:
                 if 'command' in st:
                     command_list.append(Command(st['command']))
-    return command_list
+        elif 'branch' in stat:
+            branch_report = branch_report + '\n'.join(stat['content']) + '\n\n'
+    if branch_report:
+        # add prefix
+        branch_report = '\nNon-deterministic branching statement: \n' + \
+                        branch_report
+    return command_list, branch_report
 
 
 def load_from_cache(layer, redo=False):
@@ -478,7 +486,7 @@ def filter_install_commands(shell_command_line):
         3. Return installed command objects, and messages for ignored commands
         and unrecognized commands'''
     report = ''
-    command_list = get_shell_commands(shell_command_line)
+    command_list, branch_report = get_shell_commands(shell_command_line)
     for command in command_list:
         command_lib.set_command_attrs(command)
     ignore_msgs, filter1 = remove_ignored_commands(command_list)
@@ -487,7 +495,8 @@ def filter_install_commands(shell_command_line):
         report = report + formats.ignored + ignore_msgs
     if unrec_msgs:
         report = report + formats.unrecognized + unrec_msgs
-
+    if branch_report:
+        report = report + branch_report
     return consolidate_commands(filter2), report
 
 


Test patch:
diff --git a/tests/test_analyze_common.py b/tests/test_analyze_common.py
index e40445c..82aba50 100644
--- a/tests/test_analyze_common.py
+++ b/tests/test_analyze_common.py
@@ -32,10 +32,18 @@ class TestAnalyzeCommon(unittest.TestCase):
         del self.test_dockerfile
 
     def testGetShellCommands(self):
-        command = common.get_shell_commands("yum install nfs-utils")
+        command, _ = common.get_shell_commands("yum install nfs-utils")
         self.assertEqual(type(command), list)
         self.assertEqual(len(command), 1)
         self.assertEqual(command[0].options, self.command1.options)
+        # test on branching command
+        branching_script = "if [ -z $var ]; then yum install nfs-utils; fi"
+        branch_command, report = common.get_shell_commands(branching_script)
+        self.assertEqual(type(branch_command), list)
+        # we will ignore branching command, so len should be 0
+        self.assertEqual(len(branch_command), 0)
+        # and the report should not be None
+        self.assertTrue(report)
 
     def testLoadFromCache(self):
         '''Given a layer object, populate the given layer in case the cache isn't empty'''