From 089226e372e91829e37084531572ccc2c43bbc09 Mon Sep 17 00:00:00 2001 From: Patrick Ogenstad Date: Fri, 11 Aug 2017 17:42:42 +0200 Subject: [PATCH] Fixes for broken asa_config module (#27218) * Fixes for broken asa_config module * strip() prompt before checking --- lib/ansible/module_utils/asa.py | 13 ++- lib/ansible/modules/network/asa/asa_config.py | 55 +++++------ lib/ansible/plugins/action/asa.py | 5 - lib/ansible/plugins/action/asa_config.py | 95 ++++++++++++++++++- lib/ansible/plugins/terminal/asa.py | 18 +++- 5 files changed, 136 insertions(+), 50 deletions(-) diff --git a/lib/ansible/module_utils/asa.py b/lib/ansible/module_utils/asa.py index 8759ece17b..199ab99a08 100644 --- a/lib/ansible/module_utils/asa.py +++ b/lib/ansible/module_utils/asa.py @@ -103,9 +103,10 @@ def to_commands(module, commands): def run_commands(module, commands, check_rc=True): - commands = to_commands(module, to_list(commands)) connection = get_connection(module) + commands = to_commands(module, to_list(commands)) + responses = list() for cmd in commands: @@ -116,9 +117,13 @@ def run_commands(module, commands, check_rc=True): def get_config(module, flags=[]): - cmd = 'show running-config ' - cmd += ' '.join(flags) - cmd = cmd.strip() + passwords = module.params['passwords'] + if passwords: + cmd = 'more system:running-config' + else: + cmd = 'show running-config ' + cmd += ' '.join(flags) + cmd = cmd.strip() try: return _DEVICE_CONFIGS[cmd] diff --git a/lib/ansible/modules/network/asa/asa_config.py b/lib/ansible/modules/network/asa/asa_config.py index e67ff2e81a..c71c576514 100644 --- a/lib/ansible/modules/network/asa/asa_config.py +++ b/lib/ansible/modules/network/asa/asa_config.py @@ -172,7 +172,6 @@ vars: password: cisco authorize: yes auth_pass: cisco - transport: cli --- - asa_config: @@ -216,30 +215,14 @@ backup_path: returned: when backup is yes type: string sample: /playbooks/ansible/backup/asa_config.2016-07-16@22:28:34 -responses: - description: The set of responses from issuing the commands on the device - returned: when not check_mode - type: list - sample: ['...', '...'] """ -import traceback - -from ansible.module_utils.network import NetworkModule, NetworkError +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.asa import asa_argument_spec, check_args +from ansible.module_utils.asa import get_config, load_config, run_commands from ansible.module_utils.netcfg import NetworkConfig, dumps from ansible.module_utils._text import to_native -def get_config(module): - contents = module.params['config'] - if not contents: - if module.params['defaults']: - include = 'defaults' - elif module.params['passwords']: - include = 'passwords' - else: - include = None - contents = module.config.get_config(include=include) - return NetworkConfig(indent=1, contents=contents) def get_candidate(module): candidate = NetworkConfig(indent=1) @@ -256,11 +239,14 @@ def run(module, result): path = module.params['parents'] candidate = get_candidate(module) - if match != 'none': - config = get_config(module) - configobjs = candidate.difference(config, path=path, match=match, - replace=replace) + contents = module.params['config'] + if not contents: + contents = get_config(module) + config = NetworkConfig(indent=1, contents=contents) + configobjs = candidate.difference(config, path=path, match=match, + replace=replace) + else: configobjs = candidate.items @@ -279,7 +265,7 @@ def run(module, result): # send the configuration commands to the device and merge # them with the current running config if not module.check_mode: - result['responses'] = module.config.load_config(commands) + load_config(module, commands) result['changed'] = True if module.params['save']: @@ -310,27 +296,30 @@ def main(): save=dict(type='bool', default=False), ) + argument_spec.update(asa_argument_spec) + mutually_exclusive = [('lines', 'src'), ('defaults', 'passwords')] required_if = [('match', 'strict', ['lines']), ('match', 'exact', ['lines']), ('replace', 'block', ['lines'])] - module = NetworkModule(argument_spec=argument_spec, - connect_on_load=False, + module = AnsibleModule(argument_spec=argument_spec, mutually_exclusive=mutually_exclusive, required_if=required_if, supports_check_mode=True) - result = dict(changed=False) + result = {'changed': False} + + check_args(module) + + config = None + if module.params['backup']: - result['__backup__'] = module.config.get_config() + result['__backup__'] = get_config(module) - try: - run(module, result) - except NetworkError as e: - module.fail_json(msg=to_native(e), exception=traceback.format_exc(), **e.kwargs) + run(module, result) module.exit_json(**result) diff --git a/lib/ansible/plugins/action/asa.py b/lib/ansible/plugins/action/asa.py index 9cbd342c0d..6b14f0d60c 100644 --- a/lib/ansible/plugins/action/asa.py +++ b/lib/ansible/plugins/action/asa.py @@ -77,11 +77,6 @@ class ActionModule(_ActionModule): result = super(ActionModule, self).run(tmp, task_vars) - # take the shell out of enable mode - if pc.become: - req = json.dumps(request_builder('get', 'disable')) - out = connection.exec_command(req) - return result def load_provider(self): diff --git a/lib/ansible/plugins/action/asa_config.py b/lib/ansible/plugins/action/asa_config.py index 590bc332af..98200856e4 100644 --- a/lib/ansible/plugins/action/asa_config.py +++ b/lib/ansible/plugins/action/asa_config.py @@ -1,5 +1,5 @@ # -# Copyright 2015 Peter Sprygada +# (c) 2017, Red Hat, Inc. # # This file is part of Ansible # @@ -19,9 +19,94 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from ansible.plugins.action import ActionBase -from ansible.plugins.action.net_config import ActionModule as NetActionModule +import os +import re +import time +import glob + +from ansible.plugins.action.asa import ActionModule as _ActionModule +from ansible.module_utils._text import to_text +from ansible.module_utils.six.moves.urllib.parse import urlsplit +from ansible.utils.vars import merge_hash + +PRIVATE_KEYS_RE = re.compile('__.+__') -class ActionModule(NetActionModule, ActionBase): - pass +class ActionModule(_ActionModule): + + def run(self, tmp=None, task_vars=None): + + if self._task.args.get('src'): + try: + self._handle_template() + except ValueError as exc: + return dict(failed=True, msg=exc.message) + + result = super(ActionModule, self).run(tmp, task_vars) + + if self._task.args.get('backup') and result.get('__backup__'): + # User requested backup and no error occurred in module. + # NOTE: If there is a parameter error, _backup key may not be in results. + filepath = self._write_backup(task_vars['inventory_hostname'], + result['__backup__']) + + result['backup_path'] = filepath + + # strip out any keys that have two leading and two trailing + # underscore characters + for key in result.keys(): + if PRIVATE_KEYS_RE.match(key): + del result[key] + + return result + + def _get_working_path(self): + cwd = self._loader.get_basedir() + if self._task._role is not None: + cwd = self._task._role._role_path + return cwd + + def _write_backup(self, host, contents): + backup_path = self._get_working_path() + '/backup' + if not os.path.exists(backup_path): + os.mkdir(backup_path) + for fn in glob.glob('%s/%s*' % (backup_path, host)): + os.remove(fn) + tstamp = time.strftime("%Y-%m-%d@%H:%M:%S", time.localtime(time.time())) + filename = '%s/%s_config.%s' % (backup_path, host, tstamp) + open(filename, 'w').write(contents) + return filename + + def _handle_template(self): + src = self._task.args.get('src') + working_path = self._get_working_path() + + if os.path.isabs(src) or urlsplit('src').scheme: + source = src + else: + source = self._loader.path_dwim_relative(working_path, 'templates', src) + if not source: + source = self._loader.path_dwim_relative(working_path, src) + + if not os.path.exists(source): + raise ValueError('path specified in src not found') + + try: + with open(source, 'r') as f: + template_data = to_text(f.read()) + except IOError: + return dict(failed=True, msg='unable to load src file') + + # Create a template search path in the following order: + # [working_path, self_role_path, dependent_role_paths, dirname(source)] + searchpath = [working_path] + if self._task._role is not None: + searchpath.append(self._task._role._role_path) + if hasattr(self._task, "_block:"): + dep_chain = self._task._block.get_dep_chain() + if dep_chain is not None: + for role in dep_chain: + searchpath.append(role._role_path) + searchpath.append(os.path.dirname(source)) + self._templar.environment.loader.searchpath = searchpath + self._task.args['src'] = self._templar.template(template_data) diff --git a/lib/ansible/plugins/terminal/asa.py b/lib/ansible/plugins/terminal/asa.py index 2186900f15..e03856d675 100644 --- a/lib/ansible/plugins/terminal/asa.py +++ b/lib/ansible/plugins/terminal/asa.py @@ -36,11 +36,22 @@ class TerminalModule(TerminalBase): terminal_stderr_re = [ re.compile(r"error:", re.I), - re.compile(r"^Removing.* not allowed") + re.compile(br"Removing.* not allowed, it is being used") ] - def on_authorize(self, passwd=None): + def on_open_shell(self): if self._get_prompt().endswith(b'#'): + self.disable_pager() + + def disable_pager(self): + cmd = {u'command': u'no terminal pager'} + try: + self._exec_cli_command(u'no terminal pager') + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to disable terminal pager') + + def on_authorize(self, passwd=None): + if self._get_prompt().strip().endswith(b'#'): return cmd = {u'command': u'enable'} @@ -52,6 +63,7 @@ class TerminalModule(TerminalBase): try: self._exec_cli_command(to_bytes(json.dumps(cmd), errors='surrogate_or_strict')) - self._exec_cli_command(u'no terminal pager') except AnsibleConnectionFailure: raise AnsibleConnectionFailure('unable to elevate privilege to enable mode') + + self.disable_pager()