update asa to use network_cli connection plugin (#26899)
* WIP update asa to use network_cli connection plugin * add asa.py to cliconf plugins * update asa.py terminal plugin to support regexp and events * update constants to map asa modules to asa action handler * update asa action handler to implement persistent connections * update asa shared module to use persistent connections * update asa_command module to use new connection * fixed pep8 issues
This commit is contained in:
parent
e976f299f8
commit
8e2dcaf9f6
6 changed files with 354 additions and 167 deletions
|
@ -448,7 +448,7 @@ DEFAULT_BECOME_ASK_PASS:
|
|||
vars: []
|
||||
yaml: {key: privilege_escalation.become_ask_pass}
|
||||
DEFAULT_BECOME_EXE:
|
||||
default:
|
||||
default:
|
||||
desc: 'TODO: write it'
|
||||
env: [{name: ANSIBLE_BECOME_EXE}]
|
||||
ini:
|
||||
|
@ -456,7 +456,7 @@ DEFAULT_BECOME_EXE:
|
|||
vars: []
|
||||
yaml: {key: privilege_escalation.become_exe}
|
||||
DEFAULT_BECOME_FLAGS:
|
||||
default:
|
||||
default:
|
||||
desc: 'TODO: write it'
|
||||
env: [{name: ANSIBLE_BECOME_FLAGS}]
|
||||
ini:
|
||||
|
@ -542,7 +542,7 @@ DEFAULT_EXECUTABLE:
|
|||
vars: []
|
||||
yaml: {key: defaults.executable}
|
||||
DEFAULT_FACT_PATH:
|
||||
default:
|
||||
default:
|
||||
desc: 'TODO: write it'
|
||||
env: [{name: ANSIBLE_FACT_PATH}]
|
||||
ini:
|
||||
|
@ -650,7 +650,7 @@ DEFAULT_INVENTORY_PLUGIN_PATH:
|
|||
vars: []
|
||||
yaml: {key: defaults.inventory_plugins}
|
||||
DEFAULT_JINJA2_EXTENSIONS:
|
||||
default:
|
||||
default:
|
||||
desc: 'TODO: write it'
|
||||
env: [{name: ANSIBLE_JINJA2_EXTENSIONS}]
|
||||
ini:
|
||||
|
@ -797,7 +797,7 @@ DEFAULT_NO_TARGET_SYSLOG:
|
|||
vars: []
|
||||
yaml: {key: defaults.no_target_syslog}
|
||||
DEFAULT_NULL_REPRESENTATION:
|
||||
default:
|
||||
default:
|
||||
desc: 'TODO: write it'
|
||||
env: [{name: ANSIBLE_NULL_REPRESENTATION}]
|
||||
ini:
|
||||
|
@ -815,7 +815,7 @@ DEFAULT_POLL_INTERVAL:
|
|||
vars: []
|
||||
yaml: {key: defaults.poll_interval}
|
||||
DEFAULT_PRIVATE_KEY_FILE:
|
||||
default:
|
||||
default:
|
||||
desc: 'TODO: write it'
|
||||
env: [{name: ANSIBLE_PRIVATE_KEY_FILE}]
|
||||
ini:
|
||||
|
@ -833,7 +833,7 @@ DEFAULT_PRIVATE_ROLE_VARS:
|
|||
vars: []
|
||||
yaml: {key: defaults.private_role_vars}
|
||||
DEFAULT_REMOTE_PORT:
|
||||
default:
|
||||
default:
|
||||
desc: 'TODO: write it'
|
||||
env: [{name: ANSIBLE_REMOTE_PORT}]
|
||||
ini:
|
||||
|
@ -851,7 +851,7 @@ DEFAULT_REMOTE_TMP:
|
|||
- name: ansible_remote_tmp
|
||||
yaml: {key: defaults.remote_tmp}
|
||||
DEFAULT_REMOTE_USER:
|
||||
default:
|
||||
default:
|
||||
desc: 'TODO: write it'
|
||||
env: [{name: ANSIBLE_REMOTE_USER}]
|
||||
ini:
|
||||
|
@ -904,7 +904,7 @@ DEFAULT_SQUASH_ACTIONS:
|
|||
vars: []
|
||||
yaml: {key: defaults.squash_actions}
|
||||
DEFAULT_SSH_TRANSFER_METHOD:
|
||||
default:
|
||||
default:
|
||||
desc: 'TODO: write it'
|
||||
env: [{name: ANSIBLE_SSH_TRANSFER_METHOD}]
|
||||
ini:
|
||||
|
@ -955,7 +955,7 @@ DEFAULT_SUDO:
|
|||
vars: []
|
||||
yaml: {key: defaults.sudo}
|
||||
DEFAULT_SUDO_EXE:
|
||||
default:
|
||||
default:
|
||||
desc: 'TODO: write it'
|
||||
env: [{name: ANSIBLE_SUDO_EXE}]
|
||||
ini:
|
||||
|
@ -979,7 +979,7 @@ DEFAULT_SUDO_USER:
|
|||
vars: []
|
||||
yaml: {key: defaults.sudo_user}
|
||||
DEFAULT_SU_EXE:
|
||||
default:
|
||||
default:
|
||||
desc: 'TODO: write it'
|
||||
env: [{name: ANSIBLE_SU_EXE}]
|
||||
ini:
|
||||
|
@ -987,7 +987,7 @@ DEFAULT_SU_EXE:
|
|||
vars: []
|
||||
yaml: {key: defaults.su_exe}
|
||||
DEFAULT_SU_FLAGS:
|
||||
default:
|
||||
default:
|
||||
desc: 'TODO: write it'
|
||||
env: [{name: ANSIBLE_SU_FLAGS}]
|
||||
ini:
|
||||
|
@ -1154,7 +1154,7 @@ GALAXY_IGNORE_CERTS:
|
|||
vars: []
|
||||
yaml: {key: galaxy.ignore_certs}
|
||||
GALAXY_ROLE_SKELETON:
|
||||
default:
|
||||
default:
|
||||
desc: 'TODO: write it'
|
||||
env: [{name: ANSIBLE_GALAXY_ROLE_SKELETON}]
|
||||
ini:
|
||||
|
@ -1243,7 +1243,7 @@ MERGE_MULTIPLE_CLI_TAGS:
|
|||
vars: []
|
||||
yaml: {key: defaults.merge_multiple_cli_tags}
|
||||
NETWORK_GROUP_MODULES:
|
||||
default: [eos, nxos, ios, iosxr, junos, ce, vyos, sros, dellos9, dellos10, dellos6]
|
||||
default: [eos, nxos, ios, iosxr, junos, ce, vyos, sros, dellos9, dellos10, dellos6, asa]
|
||||
desc: 'TODO: write it'
|
||||
env: [{name: NETWORK_GROUP_MODULES}]
|
||||
ini:
|
||||
|
@ -1282,7 +1282,7 @@ PARAMIKO_LOOK_FOR_KEYS:
|
|||
vars: []
|
||||
yaml: {key: paramiko_connection.look_for_keys}
|
||||
PARAMIKO_PROXY_COMMAND:
|
||||
default:
|
||||
default:
|
||||
desc: 'TODO: write it'
|
||||
env: [{name: ANSIBLE_PARAMIKO_PROXY_COMMAND}]
|
||||
ini:
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
# still belong to the author of the module, and may assign their own license
|
||||
# to the complete work.
|
||||
#
|
||||
# Copyright (c) 2016 Peter Sprygada, <psprygada@ansible.com>
|
||||
# Copyright (c) 2016 Patrick Ogenstad, <@ogenstad>
|
||||
# (c) 2016 Red Hat Inc.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
|
@ -26,90 +25,126 @@
|
|||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.basic import env_fallback, return_values
|
||||
from ansible.module_utils.network_common import to_list, EntityCollection
|
||||
from ansible.module_utils.connection import Connection
|
||||
|
||||
import re
|
||||
_DEVICE_CONFIGS = {}
|
||||
_CONNECTION = None
|
||||
|
||||
from ansible.module_utils.network import NetworkError, NetworkModule
|
||||
from ansible.module_utils.network import add_argument, register_transport
|
||||
from ansible.module_utils.network import to_list
|
||||
from ansible.module_utils.shell import CliBase
|
||||
from ansible.module_utils.netcli import Command
|
||||
asa_argument_spec = {
|
||||
'host': dict(),
|
||||
'port': dict(type='int'),
|
||||
'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])),
|
||||
'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True),
|
||||
'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'),
|
||||
'authorize': dict(fallback=(env_fallback, ['ANSIBLE_NET_AUTHORIZE']), type='bool'),
|
||||
'auth_pass': dict(fallback=(env_fallback, ['ANSIBLE_NET_AUTH_PASS']), no_log=True),
|
||||
'timeout': dict(type='int'),
|
||||
'provider': dict(type='dict'),
|
||||
'context': dict()
|
||||
}
|
||||
|
||||
add_argument('context', dict(required=False))
|
||||
command_spec = {
|
||||
'command': dict(key=True),
|
||||
'prompt': dict(),
|
||||
'answer': dict()
|
||||
}
|
||||
|
||||
|
||||
class Cli(CliBase):
|
||||
def get_argspec():
|
||||
return asa_argument_spec
|
||||
|
||||
CLI_PROMPTS_RE = [
|
||||
re.compile(r"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"),
|
||||
re.compile(r"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$")
|
||||
]
|
||||
|
||||
CLI_ERRORS_RE = [
|
||||
re.compile(r"error:", re.I),
|
||||
re.compile(r"^Removing.* not allowed")
|
||||
]
|
||||
def check_args(module):
|
||||
provider = module.params['provider'] or {}
|
||||
|
||||
NET_PASSWD_RE = re.compile(r"[\r\n]?password: $", re.I)
|
||||
for key in asa_argument_spec:
|
||||
if key not in ['provider', 'authorize'] and module.params[key]:
|
||||
module.warn('argument %s has been deprecated and will be removed in a future version' % key)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if provider:
|
||||
for param in ('auth_pass', 'password'):
|
||||
if provider.get(param):
|
||||
module.no_log_values.update(return_values(provider[param]))
|
||||
|
||||
super(Cli, self).__init__(*args, **kwargs)
|
||||
self.default_output = 'text'
|
||||
|
||||
def connect(self, params, **kwargs):
|
||||
super(Cli, self).connect(params, kickstart=False, **kwargs)
|
||||
def get_connection(module):
|
||||
global _CONNECTION
|
||||
if _CONNECTION:
|
||||
return _CONNECTION
|
||||
_CONNECTION = Connection(module)
|
||||
|
||||
if params['context']:
|
||||
self.change_context(params, **kwargs)
|
||||
context = module.params['context']
|
||||
|
||||
def authorize(self, params, **kwargs):
|
||||
passwd = params['auth_pass']
|
||||
errors = self.shell.errors
|
||||
# Disable errors (if already in enable mode)
|
||||
self.shell.errors = []
|
||||
cmd = Command('enable', prompt=self.NET_PASSWD_RE, response=passwd)
|
||||
self.execute([cmd, 'no terminal pager'])
|
||||
# Reapply error handling
|
||||
self.shell.errors = errors
|
||||
|
||||
def change_context(self, params):
|
||||
context = params['context']
|
||||
if context:
|
||||
if context == 'system':
|
||||
command = 'changeto system'
|
||||
else:
|
||||
command = 'changeto context %s' % context
|
||||
_CONNECTION.get(command)
|
||||
|
||||
self.execute(command)
|
||||
return _CONNECTION
|
||||
|
||||
# Config methods
|
||||
|
||||
def configure(self, commands):
|
||||
cmds = ['configure terminal']
|
||||
cmds.extend(to_list(commands))
|
||||
if cmds[-1] == 'exit':
|
||||
cmds[-1] = 'end'
|
||||
elif cmds[-1] != 'end':
|
||||
cmds.append('end')
|
||||
responses = self.execute(cmds)
|
||||
return responses[1:]
|
||||
def to_commands(module, commands):
|
||||
assert isinstance(commands, list), 'argument must be of type <list>'
|
||||
|
||||
def get_config(self, include=None):
|
||||
if include not in [None, 'defaults', 'passwords']:
|
||||
raise ValueError('include must be one of None, defaults, passwords')
|
||||
cmd = 'show running-config'
|
||||
if include == 'passwords':
|
||||
cmd = 'more system:running-config'
|
||||
elif include == 'defaults':
|
||||
cmd = 'show running-config all'
|
||||
else:
|
||||
cmd = 'show running-config'
|
||||
return self.run_commands(cmd)[0]
|
||||
transform = EntityCollection(module, command_spec)
|
||||
commands = transform(commands)
|
||||
|
||||
def load_config(self, commands):
|
||||
return self.configure(commands)
|
||||
for index, item in enumerate(commands):
|
||||
if module.check_mode and not item['command'].startswith('show'):
|
||||
module.warn('only show commands are supported when using check '
|
||||
'mode, not executing `%s`' % item['command'])
|
||||
|
||||
def save_config(self):
|
||||
self.execute(['write memory'])
|
||||
return commands
|
||||
|
||||
Cli = register_transport('cli', default=True)(Cli)
|
||||
|
||||
def run_commands(module, commands, check_rc=True):
|
||||
commands = to_commands(module, to_list(commands))
|
||||
connection = get_connection(module)
|
||||
|
||||
responses = list()
|
||||
|
||||
for cmd in commands:
|
||||
out = connection.get(**cmd)
|
||||
responses.append(to_text(out, errors='surrogate_then_replace'))
|
||||
|
||||
return responses
|
||||
|
||||
|
||||
def get_config(module, flags=[]):
|
||||
cmd = 'show running-config '
|
||||
cmd += ' '.join(flags)
|
||||
cmd = cmd.strip()
|
||||
|
||||
try:
|
||||
return _DEVICE_CONFIGS[cmd]
|
||||
except KeyError:
|
||||
conn = get_connection(module)
|
||||
out = conn.get(cmd)
|
||||
cfg = to_text(out, errors='surrogate_then_replace').strip()
|
||||
_DEVICE_CONFIGS[cmd] = cfg
|
||||
return cfg
|
||||
|
||||
|
||||
def load_config(module, config):
|
||||
conn = get_connection(module)
|
||||
conn.edit_config(config)
|
||||
|
||||
|
||||
def get_defaults_flag(module):
|
||||
rc, out, err = exec_command(module, 'show running-config ?')
|
||||
out = to_text(out, errors='surrogate_then_replace')
|
||||
|
||||
commands = set()
|
||||
for line in out.splitlines():
|
||||
if line:
|
||||
commands.add(line.strip().split()[0])
|
||||
|
||||
if 'all' in commands:
|
||||
return 'all'
|
||||
else:
|
||||
return 'full'
|
||||
|
|
|
@ -133,28 +133,18 @@ failed_conditions:
|
|||
type: list
|
||||
sample: ['...', '...']
|
||||
"""
|
||||
from ansible.module_utils.basic import get_exception
|
||||
from ansible.module_utils.netcli import CommandRunner
|
||||
from ansible.module_utils.netcli import AddCommandError, FailedConditionsError
|
||||
from ansible.module_utils.asa 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 run_commands
|
||||
from ansible.module_utils.six import string_types
|
||||
|
||||
VALID_KEYS = ['command', 'prompt', 'response']
|
||||
|
||||
def to_lines(stdout):
|
||||
for item in stdout:
|
||||
if isinstance(item, basestring):
|
||||
if isinstance(item, string_types):
|
||||
item = str(item).split('\n')
|
||||
yield item
|
||||
|
||||
def parse_commands(module):
|
||||
for cmd in module.params['commands']:
|
||||
if isinstance(cmd, basestring):
|
||||
cmd = dict(command=cmd, output=None)
|
||||
elif 'command' not in cmd:
|
||||
module.fail_json(msg='command keyword argument is required')
|
||||
elif not set(cmd.keys()).issubset(VALID_KEYS):
|
||||
module.fail_json(msg='unknown keyword specified')
|
||||
yield cmd
|
||||
|
||||
def main():
|
||||
spec = dict(
|
||||
|
@ -168,59 +158,48 @@ def main():
|
|||
interval=dict(default=1, type='int')
|
||||
)
|
||||
|
||||
module = NetworkModule(argument_spec=spec,
|
||||
connect_on_load=False,
|
||||
supports_check_mode=True)
|
||||
spec.update(asa_argument_spec)
|
||||
|
||||
commands = list(parse_commands(module))
|
||||
conditionals = module.params['wait_for'] or list()
|
||||
module = AnsibleModule(argument_spec=spec, supports_check_mode=True)
|
||||
check_args(module)
|
||||
|
||||
warnings = list()
|
||||
result = {'changed': False}
|
||||
|
||||
runner = CommandRunner(module)
|
||||
wait_for = module.params['wait_for'] or list()
|
||||
conditionals = [Conditional(c) for c in wait_for]
|
||||
|
||||
for cmd in commands:
|
||||
if module.check_mode and not cmd['command'].startswith('show'):
|
||||
warnings.append('only show commands are supported when using '
|
||||
'check mode, not executing `%s`' % cmd['command'])
|
||||
else:
|
||||
if cmd['command'].startswith('conf'):
|
||||
module.fail_json(msg='asa_command does not support running '
|
||||
'config mode commands. Please use '
|
||||
'asa_config instead')
|
||||
try:
|
||||
runner.add_command(**cmd)
|
||||
except AddCommandError:
|
||||
exc = get_exception()
|
||||
warnings.append('duplicate command detected: %s' % cmd)
|
||||
commands = module.params['commands']
|
||||
retries = module.params['retries']
|
||||
interval = module.params['interval']
|
||||
match = module.params['match']
|
||||
|
||||
for item in conditionals:
|
||||
runner.add_conditional(item)
|
||||
while retries > 0:
|
||||
responses = run_commands(module, commands)
|
||||
|
||||
runner.retries = module.params['retries']
|
||||
runner.interval = module.params['interval']
|
||||
runner.match = module.params['match']
|
||||
for item in list(conditionals):
|
||||
if item(responses):
|
||||
if match == 'any':
|
||||
conditionals = list()
|
||||
break
|
||||
conditionals.remove(item)
|
||||
|
||||
try:
|
||||
runner.run()
|
||||
except FailedConditionsError:
|
||||
exc = get_exception()
|
||||
module.fail_json(msg=str(exc), failed_conditions=exc.failed_conditions)
|
||||
except NetworkError:
|
||||
exc = get_exception()
|
||||
module.fail_json(msg=str(exc))
|
||||
if not conditionals:
|
||||
break
|
||||
|
||||
result = dict(changed=False, stdout=list())
|
||||
time.sleep(interval)
|
||||
retries -= 1
|
||||
|
||||
for cmd in commands:
|
||||
try:
|
||||
output = runner.get_command(cmd['command'])
|
||||
except ValueError:
|
||||
output = 'command not executed due to check_mode, see warnings'
|
||||
result['stdout'].append(output)
|
||||
if conditionals:
|
||||
failed_conditions = [item.raw for item in conditionals]
|
||||
msg = 'One or more conditional statements have not be satisfied'
|
||||
module.fail_json(msg=msg, failed_conditions=failed_conditions)
|
||||
|
||||
result['warnings'] = warnings
|
||||
result['stdout_lines'] = list(to_lines(result['stdout']))
|
||||
|
||||
result.update({
|
||||
'changed': False,
|
||||
'stdout': responses,
|
||||
'stdout_lines': list(to_lines(responses))
|
||||
})
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
|
111
lib/ansible/plugins/action/asa.py
Normal file
111
lib/ansible/plugins/action/asa.py
Normal file
|
@ -0,0 +1,111 @@
|
|||
#
|
||||
# (c) 2016 Red Hat Inc.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import sys
|
||||
import copy
|
||||
import json
|
||||
|
||||
from ansible.plugins.action.normal import ActionModule as _ActionModule
|
||||
from ansible.module_utils.basic import AnsibleFallbackNotFound
|
||||
from ansible.module_utils.asa import asa_argument_spec
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible.module_utils.connection import request_builder
|
||||
|
||||
try:
|
||||
from __main__ import display
|
||||
except ImportError:
|
||||
from ansible.utils.display import Display
|
||||
display = Display()
|
||||
|
||||
|
||||
class ActionModule(_ActionModule):
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
|
||||
if self._play_context.connection != 'local':
|
||||
return dict(
|
||||
failed=True,
|
||||
msg='invalid connection specified, expected connection=local, '
|
||||
'got %s' % self._play_context.connection
|
||||
)
|
||||
|
||||
provider = self.load_provider()
|
||||
|
||||
pc = copy.deepcopy(self._play_context)
|
||||
pc.connection = 'network_cli'
|
||||
pc.network_os = 'asa'
|
||||
pc.remote_addr = provider['host'] or self._play_context.remote_addr
|
||||
pc.port = provider['port'] or self._play_context.port or 22
|
||||
pc.remote_user = provider['username'] or self._play_context.connection_user
|
||||
pc.password = provider['password'] or self._play_context.password
|
||||
pc.private_key_file = provider['ssh_keyfile'] or self._play_context.private_key_file
|
||||
pc.timeout = provider['timeout'] or self._play_context.timeout
|
||||
pc.become = provider['authorize'] or False
|
||||
pc.become_pass = provider['auth_pass']
|
||||
|
||||
display.vvv('using connection plugin %s' % pc.connection, pc.remote_addr)
|
||||
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
|
||||
|
||||
socket_path = connection.run()
|
||||
|
||||
display.vvvv('socket_path: %s' % socket_path, pc.remote_addr)
|
||||
if not socket_path:
|
||||
return {'failed': True,
|
||||
'msg': 'unable to open shell. Please see: ' +
|
||||
'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'}
|
||||
|
||||
task_vars['ansible_socket'] = socket_path
|
||||
|
||||
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):
|
||||
provider = self._task.args.get('provider', {})
|
||||
for key, value in iteritems(asa_argument_spec):
|
||||
if key != 'provider' and key not in provider:
|
||||
if key in self._task.args:
|
||||
provider[key] = self._task.args[key]
|
||||
elif 'fallback' in value:
|
||||
provider[key] = self._fallback(value['fallback'])
|
||||
elif key not in provider:
|
||||
provider[key] = None
|
||||
return provider
|
||||
|
||||
def _fallback(self, fallback):
|
||||
strategy = fallback[0]
|
||||
args = []
|
||||
kwargs = {}
|
||||
|
||||
for item in fallback[1:]:
|
||||
if isinstance(item, dict):
|
||||
kwargs = item
|
||||
else:
|
||||
args = item
|
||||
try:
|
||||
return strategy(*args, **kwargs)
|
||||
except AnsibleFallbackNotFound:
|
||||
pass
|
78
lib/ansible/plugins/cliconf/asa.py
Normal file
78
lib/ansible/plugins/cliconf/asa.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
#
|
||||
# (c) 2017 Red Hat Inc.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import re
|
||||
import json
|
||||
|
||||
from itertools import chain
|
||||
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
from ansible.module_utils.network_common import to_list
|
||||
from ansible.plugins.cliconf import CliconfBase, enable_mode
|
||||
|
||||
|
||||
class Cliconf(CliconfBase):
|
||||
|
||||
def get_device_info(self):
|
||||
device_info = {}
|
||||
|
||||
device_info['network_os'] = 'asa'
|
||||
reply = self.get(b'show version')
|
||||
data = to_text(reply, errors='surrogate_or_strict').strip()
|
||||
|
||||
match = re.search(r'Version (\S+),', data)
|
||||
if match:
|
||||
device_info['network_os_version'] = match.group(1)
|
||||
|
||||
match = re.search(r'^Model Id:\s+(.+) \(revision', data, re.M)
|
||||
if match:
|
||||
device_info['network_os_model'] = match.group(1)
|
||||
|
||||
match = re.search(r'^(.+) up', data, re.M)
|
||||
if match:
|
||||
device_info['network_os_hostname'] = match.group(1)
|
||||
|
||||
return device_info
|
||||
|
||||
@enable_mode
|
||||
def get_config(self, source='running'):
|
||||
if source not in ('running', 'startup'):
|
||||
return self.invalid_params("fetching configuration from %s is not supported" % source)
|
||||
if source == 'running':
|
||||
cmd = b'show running-config all'
|
||||
else:
|
||||
cmd = b'show startup-config'
|
||||
return self.send_command(cmd)
|
||||
|
||||
@enable_mode
|
||||
def edit_config(self, command):
|
||||
for cmd in chain([b'configure terminal'], to_list(command), [b'end']):
|
||||
self.send_command(cmd)
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
return self.send_command(*args, **kwargs)
|
||||
|
||||
def get_capabilities(self):
|
||||
result = {}
|
||||
result['rpc'] = self.get_base_rpc()
|
||||
result['network_api'] = 'cliconf'
|
||||
result['device_info'] = self.get_device_info()
|
||||
return json.dumps(result)
|
|
@ -22,8 +22,9 @@ __metaclass__ = type
|
|||
import re
|
||||
import json
|
||||
|
||||
from ansible.plugins.terminal import TerminalBase
|
||||
from ansible.errors import AnsibleConnectionFailure
|
||||
from ansible.module_utils._text import to_text, to_bytes
|
||||
from ansible.plugins.terminal import TerminalBase
|
||||
|
||||
|
||||
class TerminalModule(TerminalBase):
|
||||
|
@ -34,40 +35,23 @@ class TerminalModule(TerminalBase):
|
|||
]
|
||||
|
||||
terminal_stderr_re = [
|
||||
re.compile(r"% ?Error"),
|
||||
re.compile(r"^% \w+", re.M),
|
||||
re.compile(r"% ?Bad secret"),
|
||||
re.compile(r"invalid input", re.I),
|
||||
re.compile(r"(?:incomplete|ambiguous) command", re.I),
|
||||
re.compile(r"connection timed out", re.I),
|
||||
re.compile(r"[^\r\n]+ not found", re.I),
|
||||
re.compile(r"'[^']' +returned error code: ?\d+"),
|
||||
re.compile(r"error:", re.I),
|
||||
re.compile(r"^Removing.* not allowed")
|
||||
]
|
||||
|
||||
def authorize(self, passwd=None):
|
||||
if self._get_prompt().endswith('#'):
|
||||
def on_authorize(self, passwd=None):
|
||||
if self._get_prompt().endswith(b'#'):
|
||||
return
|
||||
|
||||
cmd = {'command': 'enable'}
|
||||
cmd = {u'command': u'enable'}
|
||||
if passwd:
|
||||
cmd['prompt'] = r"[\r\n]?password: $"
|
||||
cmd['answer'] = passwd
|
||||
# Note: python-3.5 cannot combine u"" and r"" together. Thus make
|
||||
# an r string and use to_text to ensure it's text on both py2 and py3.
|
||||
cmd[u'prompt'] = to_text(r"[\r\n]?password: $", errors='surrogate_or_strict')
|
||||
cmd[u'answer'] = passwd
|
||||
|
||||
try:
|
||||
self._exec_cli_command(json.dumps(cmd))
|
||||
self._exec_cli_command('terminal pager 0')
|
||||
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')
|
||||
|
||||
def on_deauthorize(self):
|
||||
prompt = self._get_prompt()
|
||||
if prompt is None:
|
||||
# if prompt is None most likely the terminal is hung up at a prompt
|
||||
return
|
||||
|
||||
if '(config' in prompt:
|
||||
self._exec_cli_command('end')
|
||||
self._exec_cli_command('disable')
|
||||
|
||||
elif prompt.endswith('#'):
|
||||
self._exec_cli_command('disable')
|
||||
|
|
Loading…
Reference in a new issue