From 37a918438b2363a66c5d7a7bb1320b68fd4460c7 Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Fri, 25 Sep 2015 23:57:03 -0400 Subject: [PATCH] task logging revamp * allow global no_log setting, no need to set at play or task level, but can be overriden by them * allow turning off syslog only on task execution from target host (manage_syslog), overlaps with no_log functionality * created log function for task modules to use, now we can remove all syslog references, will use systemd journal if present * added debug flag to modules, so they can make it call new log function conditionally * added debug logging in module's run_command --- examples/ansible.cfg | 7 ++ lib/ansible/constants.py | 4 ++ lib/ansible/module_utils/basic.py | 89 ++++++++++++++---------- lib/ansible/playbook/play_context.py | 9 ++- lib/ansible/plugins/action/__init__.py | 6 +- test/units/playbook/test_play_context.py | 9 ++- 6 files changed, 79 insertions(+), 45 deletions(-) diff --git a/examples/ansible.cfg b/examples/ansible.cfg index e79fa4ee84..4f61dc4fbc 100644 --- a/examples/ansible.cfg +++ b/examples/ansible.cfg @@ -177,6 +177,13 @@ fact_caching = memory #retry_files_enabled = False #retry_files_save_path = ~/.ansible-retry + +# prevents logging of task data, off by default +#no_log = False + +# prevents logging of tasks, but only on the targets, data is still logged on the master/controller +#managed_syslog = True + [privilege_escalation] #become=True #become_method=sudo diff --git a/lib/ansible/constants.py b/lib/ansible/constants.py index c081c1461b..cd3e0a34a0 100644 --- a/lib/ansible/constants.py +++ b/lib/ansible/constants.py @@ -154,6 +154,10 @@ DEFAULT_LOG_PATH = get_config(p, DEFAULTS, 'log_path', 'ANSIB DEFAULT_FORCE_HANDLERS = get_config(p, DEFAULTS, 'force_handlers', 'ANSIBLE_FORCE_HANDLERS', False, boolean=True) DEFAULT_INVENTORY_IGNORE = get_config(p, DEFAULTS, 'inventory_ignore_extensions', 'ANSIBLE_INVENTORY_IGNORE', ["~", ".orig", ".bak", ".ini", ".cfg", ".retry", ".pyc", ".pyo"], islist=True) +# disclosure +DEFAULT_NO_LOG = get_config(p, DEFAULTS, 'no_log', 'ANSIBLE_NO_LOG', False, boolean=True) +DEFAULT_MANAGED_SYSLOG = get_config(p, DEFAULTS, 'managed_syslog', 'ANSIBLE_MANAGED_SYSLOG', True, boolean=True) + # selinux DEFAULT_SELINUX_SPECIAL_FS = get_config(p, 'selinux', 'special_context_filesystems', None, 'fuse, nfs, vboxsf, ramfs', islist=True) diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py index 1d6bc95252..582aa35e3b 100644 --- a/lib/ansible/module_utils/basic.py +++ b/lib/ansible/module_utils/basic.py @@ -393,6 +393,7 @@ class AnsibleModule(object): self.check_mode = False self.no_log = no_log self.cleanup_files = [] + self.debug = False self.aliases = {} @@ -407,16 +408,14 @@ class AnsibleModule(object): self.params = self._load_params() - self._legal_inputs = ['_ansible_check_mode', '_ansible_no_log'] + self._legal_inputs = ['_ansible_check_mode', '_ansible_no_log', '_ansible_debug'] + # append to legal_inputs and then possibly check against them self.aliases = self._handle_aliases() - if check_invalid_arguments: - self._check_invalid_arguments() - self._check_for_check_mode() - self._check_for_no_log() + self._check_arguments(check_invalid_arguments) - # check exclusive early + # check exclusive early if not bypass_checks: self._check_mutually_exclusive(mutually_exclusive) @@ -952,28 +951,24 @@ class AnsibleModule(object): aliases_results[alias] = k if alias in self.params: self.params[k] = self.params[alias] - + return aliases_results - def _check_for_check_mode(self): + def _check_arguments(self, check_invalid_arguments): for (k,v) in self.params.iteritems(): + if k == '_ansible_check_mode' and v: if not self.supports_check_mode: self.exit_json(skipped=True, msg="remote module does not support check mode") self.check_mode = True - break - def _check_for_no_log(self): - for (k,v) in self.params.iteritems(): - if k == '_ansible_no_log': + elif k == '_ansible_no_log': self.no_log = self.boolean(v) - def _check_invalid_arguments(self): - for (k,v) in self.params.iteritems(): - # these should be in legal inputs already - #if k in ('_ansible_check_mode', '_ansible_no_log'): - # continue - if k not in self._legal_inputs: + elif k == '_ansible_debug': + self.debug = self.boolean(v) + + elif check_invalid_arguments and k not in self._legal_inputs: self.fail_json(msg="unsupported parameter for module: %s" % k) def _count_terms(self, check): @@ -1215,6 +1210,36 @@ class AnsibleModule(object): params = dict() return params + def _log_to_syslog(self, msg): + module = 'ansible-%s' % os.path.basename(__file__) + syslog.openlog(str(module), 0, syslog.LOG_USER) + syslog.syslog(syslog.LOG_INFO, msg) + + def log(self, msg, log_args=None): + + if not self.no_log: + + if log_args is None: + log_args = dict() + + module = 'ansible-%s' % os.path.basename(__file__) + + # 6655 - allow for accented characters + if isinstance(msg, unicode): + # We should never get here as msg should be type str, not unicode + msg = msg.encode('utf-8') + + if (has_journal): + journal_args = [("MODULE", os.path.basename(__file__))] + for arg in log_args: + journal_args.append((arg.upper(), str(log_args[arg]))) + try: + journal.send("%s %s" % (module, msg), **dict(journal_args)) + except IOError: + # fall back to syslog since logging to journal failed + self._log_to_syslog(msg) + else: + self._log_to_syslog(msg) def _log_invocation(self): ''' log that ansible ran the module ''' @@ -1240,7 +1265,6 @@ class AnsibleModule(object): param_val = param_val.encode('utf-8') log_args[param] = heuristic_log_sanitize(param_val) - module = 'ansible-%s' % os.path.basename(__file__) msg = [] for arg in log_args: arg_val = log_args[arg] @@ -1254,24 +1278,9 @@ class AnsibleModule(object): else: msg = 'Invoked' - # 6655 - allow for accented characters - if isinstance(msg, unicode): - # We should never get here as msg should be type str, not unicode - msg = msg.encode('utf-8') + self.log(msg, log_args=log_args) + - if (has_journal): - journal_args = [("MODULE", os.path.basename(__file__))] - for arg in log_args: - journal_args.append((arg.upper(), str(log_args[arg]))) - try: - journal.send("%s %s" % (module, msg), **dict(journal_args)) - except IOError: - # fall back to syslog since logging to journal failed - syslog.openlog(str(module), 0, syslog.LOG_USER) - syslog.syslog(syslog.LOG_INFO, msg) #1 - else: - syslog.openlog(str(module), 0, syslog.LOG_USER) - syslog.syslog(syslog.LOG_INFO, msg) #2 def _set_cwd(self): try: @@ -1656,6 +1665,14 @@ class AnsibleModule(object): self.fail_json(rc=e.errno, msg="Could not open %s, %s" % (cwd, str(e))) try: + + if self.debug: + if isinstance(args, list): + running = ' '.join(args) + else: + running = args + self.log('Executing: ' + running) + cmd = subprocess.Popen(args, **kwargs) # the communication logic here is essentially taken from that diff --git a/lib/ansible/playbook/play_context.py b/lib/ansible/playbook/play_context.py index 1e9f35d4ae..0f3a7658aa 100644 --- a/lib/ansible/playbook/play_context.py +++ b/lib/ansible/playbook/play_context.py @@ -206,9 +206,6 @@ class PlayContext(Base): if play.become_user: self.become_user = play.become_user - # non connection related - self.no_log = play.no_log - if play.force_handlers is not None: self.force_handlers = play.force_handlers @@ -234,8 +231,6 @@ class PlayContext(Base): # general flags (should we move out?) if options.verbosity: self.verbosity = options.verbosity - #if options.no_log: - # self.no_log = boolean(options.no_log) if options.check: self.check_mode = boolean(options.check) if hasattr(options, 'force_handlers') and options.force_handlers: @@ -322,6 +317,10 @@ class PlayContext(Base): if task._local_action: setattr(new_info, 'connection', 'local') + # set no_log to default if it was not previouslly set + if new_info.no_log is None: + new_info.no_log = C.DEFAULT_NO_LOG + return new_info def make_become_cmd(self, cmd, executable=None): diff --git a/lib/ansible/plugins/action/__init__.py b/lib/ansible/plugins/action/__init__.py index d9ee141e0e..ea298d5037 100644 --- a/lib/ansible/plugins/action/__init__.py +++ b/lib/ansible/plugins/action/__init__.py @@ -351,9 +351,13 @@ class ActionBase: module_args['_ansible_check_mode'] = True # set no log in the module arguments, if required - if self._play_context.no_log: + if self._play_context.no_log or not C.DEFAULT_MANAGED_SYSLOG: module_args['_ansible_no_log'] = True + # set debug in the module arguments, if required + if C.DEFAULT_DEBUG: + module_args['_ansible_debug'] = True + (module_style, shebang, module_data) = self._configure_module(module_name=module_name, module_args=module_args, task_vars=task_vars) if not shebang: raise AnsibleError("module is missing interpreter line") diff --git a/test/units/playbook/test_play_context.py b/test/units/playbook/test_play_context.py index 552b4aa2fc..30b45e1a02 100644 --- a/test/units/playbook/test_play_context.py +++ b/test/units/playbook/test_play_context.py @@ -79,7 +79,6 @@ class TestPlayContext(unittest.TestCase): self.assertEqual(play_context.remote_user, 'mock') self.assertEqual(play_context.password, '') self.assertEqual(play_context.port, 1234) - self.assertEqual(play_context.no_log, True) self.assertEqual(play_context.become, True) self.assertEqual(play_context.become_method, "mock") self.assertEqual(play_context.become_user, "mockroot") @@ -87,11 +86,11 @@ class TestPlayContext(unittest.TestCase): mock_task = MagicMock() mock_task.connection = 'mocktask' mock_task.remote_user = 'mocktask' + mock_task.no_log = mock_play.no_log mock_task.become = True mock_task.become_method = 'mocktask' mock_task.become_user = 'mocktaskroot' mock_task.become_pass = 'mocktaskpass' - mock_task.no_log = False mock_task._local_action = False all_vars = dict( @@ -106,12 +105,16 @@ class TestPlayContext(unittest.TestCase): self.assertEqual(play_context.connection, 'mock_inventory') self.assertEqual(play_context.remote_user, 'mocktask') self.assertEqual(play_context.port, 4321) - self.assertEqual(play_context.no_log, False) + self.assertEqual(play_context.no_log, True) self.assertEqual(play_context.become, True) self.assertEqual(play_context.become_method, "mocktask") self.assertEqual(play_context.become_user, "mocktaskroot") self.assertEqual(play_context.become_pass, "mocktaskpass") + mock_task.no_log = False + play_context = play_context.set_task_and_variable_override(task=mock_task, variables=all_vars, templar=mock_templar) + self.assertEqual(play_context.no_log, False) + def test_play_context_make_become_cmd(self): (options, args) = self._parser.parse_args([]) play_context = PlayContext(options=options)