[apparmor] [PATCH v2] json support for tools (logprof and genprof)

Goldwyn Rodrigues rgoldwyn at suse.de
Wed Mar 22 18:24:04 UTC 2017


From: Goldwyn Rodrigues <rgoldwyn at suse.com>

This adds JSON support for tools in order to be able to talk to
other utilities such as Yast.

The json is one per line, in order to differentiate between multiple
records. This is based on work presented by Christian Boltz some time
back.

Signed-off-by: Goldwyn Rodrigues <rgoldwyn at suse.com>

---
Changes since v1:
 - implementation of set_json_mode(), write_json()
 - Changed the way output is provided to keep input the same. This would
   write in either of two formats: text or json, but will keep input the same.
   This helps in keeping localizations in place. I so wish UI was a class..
 - Removed all yast calls.

 utils/aa-genprof     |   4 +
 utils/aa-logprof     |   5 ++
 utils/apparmor/ui.py | 239 ++++++++++++++++++++++-----------------------------
 3 files changed, 113 insertions(+), 135 deletions(-)

diff --git a/utils/aa-genprof b/utils/aa-genprof
index e2e6544..9a8783d 100755
--- a/utils/aa-genprof
+++ b/utils/aa-genprof
@@ -61,8 +61,12 @@ parser = argparse.ArgumentParser(description=_('Generate profile for the given p
 parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
 parser.add_argument('-f', '--file', type=str, help=_('path to logfile'))
 parser.add_argument('program', type=str, help=_('name of program to profile'))
+parser.add_argument('-j', '--json', action="store_true", help=_('provide output in json format'))
 args = parser.parse_args()
 
+if args.json:
+	aaui.set_json_mode()
+
 profiling = args.program
 profiledir = args.dir
 
diff --git a/utils/aa-logprof b/utils/aa-logprof
index c05cbef..a00cfea 100755
--- a/utils/aa-logprof
+++ b/utils/aa-logprof
@@ -16,6 +16,7 @@ import argparse
 import os
 
 import apparmor.aa as apparmor
+import apparmor.ui as aaui
 
 # setup exception handling
 from apparmor.fail import enable_aa_exception_handler
@@ -29,8 +30,12 @@ parser = argparse.ArgumentParser(description=_('Process log entries to generate
 parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
 parser.add_argument('-f', '--file', type=str, help=_('path to logfile'))
 parser.add_argument('-m', '--mark', type=str, help=_('mark in the log to start processing after'))
+parser.add_argument('-j', '--json', action='store_true', help=_('provide the output in json format'))
 args = parser.parse_args()
 
+if args.json:
+    aaui.set_json_mode()
+
 profiledir = args.dir
 logmark = args.mark or ''
 
diff --git a/utils/apparmor/ui.py b/utils/apparmor/ui.py
index bfbde8c..e6fcd1c 100644
--- a/utils/apparmor/ui.py
+++ b/utils/apparmor/ui.py
@@ -1,5 +1,7 @@
 # ----------------------------------------------------------------------
 #    Copyright (C) 2013 Kshitij Gupta <kgupta8592 at gmail.com>
+#    Copyright (C) 2017 Christian Boltz <apparmor at cboltz.de>
+#    Copyright (C) 2017 SUSE Linux
 #
 #    This program is free software; you can redistribute it and/or
 #    modify it under the terms of version 2 of the GNU General Public
@@ -11,10 +13,11 @@
 #    GNU General Public License for more details.
 #
 # ----------------------------------------------------------------------
+
+import json
 import sys
 import re
 import readline
-from apparmor.yasti import yastLog, SendDataToYast, GetDataFromYast
 
 from apparmor.common import readkey, AppArmorException, DebugLogger
 
@@ -25,14 +28,20 @@ _ = init_translation()
 # Set up UI logger for separate messages from UI module
 debug_logger = DebugLogger('UI')
 
-# The operating mode: yast or text, text by default
-UI_mode = 'text'
-
 # If Python3, wrap input in raw_input so make check passes
 if not 'raw_input' in dir(__builtins__): raw_input = input
 
 ARROWS = {'A': 'UP', 'B': 'DOWN', 'C': 'RIGHT', 'D': 'LEFT'}
 
+UI_mode = 'text'
+
+def set_json_mode():
+    global UI_mode
+    UI_mode = 'json'
+
+def write_json(jsonout):
+    sys.stdout.write(json.dumps(jsonout, soft_keys=False, separators=(',', ': ')) + '\n')
+
 def getkey():
     key = readkey()
     if key == '\x1B':
@@ -47,18 +56,17 @@ def UI_Info(text):
     debug_logger.info(text)
     if UI_mode == 'text':
         sys.stdout.write(text + '\n')
-    else:
-        yastLog(text)
+    elif UI_mode == 'json':
+        jsonout = {'info': text}
+        write_json(jsonout)
 
 def UI_Important(text):
     debug_logger.debug(text)
     if UI_mode == 'text':
         sys.stdout.write('\n' + text + '\n')
-    else:
-        SendDataToYast({'type': 'dialog-error',
-                        'message': text
-                        })
-        path, yarg = GetDataFromYast()
+    elif UI_mode == 'json':
+        jsonout = {'important': text}
+        write_json(jsonout)
 
 def get_translated_hotkey(translated, cmsg=''):
     msg = 'PromptUser: ' + _('Invalid hotkey for')
@@ -76,43 +84,37 @@ def UI_YesNo(text, default):
     debug_logger.debug('UI_YesNo: %s: %s %s' % (UI_mode, text, default))
     default = default.lower()
     ans = None
-    if UI_mode == 'text':
-        yes = CMDS['CMD_YES']
-        no = CMDS['CMD_NO']
-        yeskey = get_translated_hotkey(yes).lower()
-        nokey = get_translated_hotkey(no).lower()
-        ans = 'XXXINVALIDXXX'
-        while ans not in ['y', 'n']:
+    yes = CMDS['CMD_YES']
+    no = CMDS['CMD_NO']
+    yeskey = get_translated_hotkey(yes).lower()
+    nokey = get_translated_hotkey(no).lower()
+    ans = 'XXXINVALIDXXX'
+    while ans not in ['y', 'n']:
+        if UI_mode == 'text':
             sys.stdout.write('\n' + text + '\n')
             if default == 'y':
                 sys.stdout.write('\n[%s] / %s\n' % (yes, no))
             else:
                 sys.stdout.write('\n%s / [%s]\n' % (yes, no))
-            ans = getkey()
-            if ans:
-                # Get back to english from localised answer
-                ans = ans.lower()
-                if ans == yeskey:
-                    ans = 'y'
-                elif ans == nokey:
-                    ans = 'n'
-                elif ans == 'left':
-                    default = 'y'
-                elif ans == 'right':
-                    default = 'n'
-                else:
-                    ans = 'XXXINVALIDXXX'
-                    continue  # If user presses any other button ask again
+	elif UI_mode == 'json':
+            jsonout = {'dialog': 'yesno', 'text': text, 'default': default}
+            write_json(jsonout)
+        ans = getkey()
+        if ans:
+            # Get back to english from localised answer
+            ans = ans.lower()
+            if ans == yeskey:
+                ans = 'y'
+            elif ans == nokey:
+                ans = 'n'
+            elif ans == 'left':
+                default = 'y'
+            elif ans == 'right':
+                default = 'n'
             else:
-                ans = default
-
-    else:
-        SendDataToYast({'type': 'dialog-yesno',
-                        'question': text
-                        })
-        ypath, yarg = GetDataFromYast()
-        ans = yarg['answer']
-        if not ans:
+                ans = 'XXXINVALIDXXX'
+                continue  # If user presses any other button ask again
+        else:
             ans = default
     return ans
 
@@ -120,17 +122,17 @@ def UI_YesNoCancel(text, default):
     debug_logger.debug('UI_YesNoCancel: %s: %s %s' % (UI_mode, text, default))
     default = default.lower()
     ans = None
-    if UI_mode == 'text':
-        yes = CMDS['CMD_YES']
-        no = CMDS['CMD_NO']
-        cancel = CMDS['CMD_CANCEL']
+    yes = CMDS['CMD_YES']
+    no = CMDS['CMD_NO']
+    cancel = CMDS['CMD_CANCEL']
 
-        yeskey = get_translated_hotkey(yes).lower()
-        nokey = get_translated_hotkey(no).lower()
-        cancelkey = get_translated_hotkey(cancel).lower()
+    yeskey = get_translated_hotkey(yes).lower()
+    nokey = get_translated_hotkey(no).lower()
+    cancelkey = get_translated_hotkey(cancel).lower()
 
-        ans = 'XXXINVALIDXXX'
-        while ans not in ['c', 'n', 'y']:
+    ans = 'XXXINVALIDXXX'
+    while ans not in ['c', 'n', 'y']:
+        if UI_mode == 'text':
             sys.stdout.write('\n' + text + '\n')
             if default == 'y':
                 sys.stdout.write('\n[%s] / %s / %s\n' % (yes, no, cancel))
@@ -138,36 +140,31 @@ def UI_YesNoCancel(text, default):
                 sys.stdout.write('\n%s / [%s] / %s\n' % (yes, no, cancel))
             else:
                 sys.stdout.write('\n%s / %s / [%s]\n' % (yes, no, cancel))
-            ans = getkey()
-            if ans:
-                # Get back to english from localised answer
-                ans = ans.lower()
-                if ans == yeskey:
-                    ans = 'y'
-                elif ans == nokey:
-                    ans = 'n'
-                elif ans == cancelkey:
-                    ans = 'c'
-                elif ans == 'left':
-                    if default == 'n':
-                        default = 'y'
-                    elif default == 'c':
-                        default = 'n'
-                elif ans == 'right':
-                    if default == 'y':
-                        default = 'n'
-                    elif default == 'n':
-                        default = 'c'
+	elif UI_mode == 'json':
+            jsonout = {'dialog': 'yesnocancal', 'text': text, 'default': default}
+            write_json(jsonout)
+        ans = getkey()
+        if ans:
+            # Get back to english from localised answer
+            ans = ans.lower()
+            if ans == yeskey:
+                ans = 'y'
+            elif ans == nokey:
+                ans = 'n'
+            elif ans == cancelkey:
+                ans = 'c'
+            elif ans == 'left':
+                if default == 'n':
+                    default = 'y'
+                elif default == 'c':
+                    default = 'n'
+            elif ans == 'right':
+                if default == 'y':
+                    default = 'n'
+                elif default == 'n':
+                    default = 'c'
             else:
                 ans = default
-    else:
-        SendDataToYast({'type': 'dialog-yesnocancel',
-                        'question': text
-                        })
-        ypath, yarg = GetDataFromYast()
-        ans = yarg['answer']
-        if not ans:
-            ans = default
     return ans
 
 def UI_GetString(text, default):
@@ -175,19 +172,15 @@ def UI_GetString(text, default):
     string = default
     if UI_mode == 'text':
         readline.set_startup_hook(lambda: readline.insert_text(default))
-        try:
-            string = raw_input('\n' + text)
-        except EOFError:
-            string = ''
-        finally:
-            readline.set_startup_hook()
-    else:
-        SendDataToYast({'type': 'dialog-getstring',
-                        'label': text,
-                        'default': default
-                        })
-        ypath, yarg = GetDataFromYast()
-        string = yarg['string']
+    elif UI_mode == 'json':
+        jsonout = {'dialog': 'getstring', 'text': text, 'default': default}
+	write_json(jsonout)
+    try:
+        string = raw_input('\n' + text)
+    except EOFError:
+        string = ''
+    finally:
+        readline.set_startup_hook()
     return string.strip()
 
 def UI_GetFile(file):
@@ -195,30 +188,18 @@ def UI_GetFile(file):
     filename = None
     if UI_mode == 'text':
         sys.stdout.write(file['description'] + '\n')
+    elif UI_mode == 'json':
+        jsonout = {'dialog': 'getfile', 'text': file['description']}
+        write_json(jsonout)
         filename = sys.stdin.read()
-    else:
-        file['type'] = 'dialog-getfile'
-        SendDataToYast(file)
-        ypath, yarg = GetDataFromYast()
-        if yarg['answer'] == 'okay':
-            filename = yarg['filename']
     return filename
 
 def UI_BusyStart(message):
     debug_logger.debug('UI_BusyStart: %s' % UI_mode)
-    if UI_mode == 'text':
-        UI_Info(message)
-    else:
-        SendDataToYast({'type': 'dialog-busy-start',
-                        'message': message
-                        })
-        ypath, yarg = GetDataFromYast()
+    UI_Info(message)
 
 def UI_BusyStop():
     debug_logger.debug('UI_BusyStop: %s' % UI_mode)
-    if UI_mode != 'text':
-        SendDataToYast({'type': 'dialog-busy-stop'})
-        ypath, yarg = GetDataFromYast()
 
 CMDS = {'CMD_ALLOW': _('(A)llow'),
         'CMD_OTHER': _('(M)ore'),
@@ -300,15 +281,7 @@ class PromptQuestion(object):
     def promptUser(self, params=''):
         cmd = None
         arg = None
-        if UI_mode == 'text':
-            cmd, arg = self.Text_PromptUser()
-        else:
-            self.type = 'wizard'
-            SendDataToYast(self)
-            ypath, yarg = GetDataFromYast()
-            if not cmd:
-                cmd = 'CMD_ABORT'
-            arg = yarg['selected']
+        cmd, arg = self.Text_PromptUser()
         if cmd == 'CMD_ABORT':
             confirm_and_abort()
             cmd = 'XXXINVALIDXXX'
@@ -377,6 +350,15 @@ class PromptQuestion(object):
         function_regexp += ')$'
 
         ans = 'XXXINVALIDXXX'
+        hdict = dict()
+        jsonprompt = {
+            'title': title,
+            'headers': hdict,
+            'explanation': explanation,
+            'options': options,
+            'menu_items': menu_items,
+        }
+
         while not re.search(function_regexp, ans, flags=re.IGNORECASE):
 
             prompt = '\n'
@@ -388,6 +370,7 @@ class PromptQuestion(object):
                 while header_copy:
                     header = header_copy.pop(0)
                     value = header_copy.pop(0)
+                    hdict[header] = value
                     prompt += formatstr % (header + ':', value)
                 prompt += '\n'
 
@@ -405,7 +388,10 @@ class PromptQuestion(object):
 
             prompt += ' / '.join(menu_items)
 
-            sys.stdout.write(prompt + '\n')
+            if UI_mode == 'json':
+                sys.stdout.write(json.dumps(jsonprompt, sort_keys=False,  separators=(',', ': ')) + '\n')
+            elif UI_mode == 'text':
+                sys.stdout.write(prompt + '\n')
 
             ans = getkey().lower()
 
@@ -447,25 +433,8 @@ def confirm_and_abort():
     ans = UI_YesNo(_('Are you sure you want to abandon this set of profile changes and exit?'), 'n')
     if ans == 'y':
         UI_Info(_('Abandoning all changes.'))
-        #shutdown_yast()
-        #for prof in created:
-        #    delete_profile(prof)
         sys.exit(0)
 
-def UI_ShortMessage(title, message):
-    SendDataToYast({'type': 'short-dialog-message',
-                    'headline': title,
-                    'message': message
-                    })
-    ypath, yarg = GetDataFromYast()
-
-def UI_LongMessage(title, message):
-    SendDataToYast({'type': 'long-dialog-message',
-                    'headline': title,
-                    'message': message
-                    })
-    ypath, yarg = GetDataFromYast()
-
 def is_number(number):
     try:
         return int(number)
-- 
2.10.2




More information about the AppArmor mailing list