Module util, template action and doc fragment for Cisco ASA

This commit is contained in:
ogenstad 2016-05-25 19:25:22 +02:00
parent bad293ae35
commit 6027e5b580
3 changed files with 333 additions and 0 deletions

View file

@ -0,0 +1,194 @@
#
# (c) 2016 Peter Sprygada, <psprygada@ansible.com>
# (c) 2016 Patrick Ogenstad, <@ogenstad>
#
# 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/>.
#
import re
from ansible.module_utils.basic import AnsibleModule, env_fallback, get_exception
from ansible.module_utils.shell import Shell, ShellError, Command, HAS_PARAMIKO
from ansible.module_utils.netcfg import parse
NET_PASSWD_RE = re.compile(r"[\r\n]?password: $", re.I)
NET_COMMON_ARGS = dict(
host=dict(required=True),
port=dict(default=22, type='int'),
show_command=dict(default='show running-config', choices=['show running-config', 'more system:running-config']),
username=dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])),
password=dict(no_log=True, fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD'])),
ssh_keyfile=dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'),
authorize=dict(default=False, fallback=(env_fallback, ['ANSIBLE_NET_AUTHORIZE']), type='bool'),
auth_pass=dict(no_log=True, fallback=(env_fallback, ['ANSIBLE_NET_AUTH_PASS'])),
context=dict(required=False),
provider=dict(),
timeout=dict(default=10, type='int')
)
CLI_PROMPTS_RE = [
re.compile(r"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"),
re.compile(r"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$")
]
CLI_ERRORS_RE = [
re.compile(r"% ?Error"),
re.compile(r"% ?Bad secret"),
re.compile(r"invalid input", re.I),
re.compile(r"is not valid", 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+"),
]
def to_list(val):
if isinstance(val, (list, tuple)):
return list(val)
elif val is not None:
return [val]
else:
return list()
class Cli(object):
def __init__(self, module):
self.module = module
self.shell = None
def connect(self, **kwargs):
host = self.module.params['host']
port = self.module.params['port'] or 22
username = self.module.params['username']
password = self.module.params['password']
key_filename = self.module.params['ssh_keyfile']
timeout = self.module.params['timeout']
try:
self.shell = Shell(kickstart=False, prompts_re=CLI_PROMPTS_RE,
errors_re=CLI_ERRORS_RE)
self.shell.open(host, port=port, username=username, password=password, key_filename=key_filename, timeout=timeout)
except ShellError:
e = get_exception()
msg = 'failed to connect to %s:%s - %s' % (host, port, str(e))
self.module.fail_json(msg=msg)
def change_context(self):
context = self.module.params['command']
if context == 'system':
command = 'changeto system'
else:
command = 'changeto context %s' % context
self.send(Command(command))
def authorize(self):
passwd = self.module.params['auth_pass']
self.send(Command('enable', prompt=NET_PASSWD_RE, response=passwd))
def send(self, commands):
return self.shell.send(commands)
class NetworkModule(AnsibleModule):
def __init__(self, *args, **kwargs):
super(NetworkModule, self).__init__(*args, **kwargs)
self.connection = None
self._config = None
self._connected = False
self.filter = None
@property
def connected(self):
return self._connected
@property
def config(self):
if not self._config:
self._config = self.get_config()
return self._config
def _load_params(self):
super(NetworkModule, self)._load_params()
provider = self.params.get('provider') or dict()
for key, value in provider.items():
if key in NET_COMMON_ARGS.keys():
if self.params.get(key) is None and value is not None:
self.params[key] = value
def connect(self):
self.connection = Cli(self)
self.connection.connect()
if self.params['authorize']:
self.connection.authorize()
self.connection.send('no terminal pager')
if self.params['context']:
if self.params['context'] == 'system':
self.connection.send('changeto system')
else:
self.connection.send('changeto context %s' % self.params['context'])
self._connected = True
def configure(self, commands):
commands = to_list(commands)
commands.insert(0, 'configure terminal')
responses = self.execute(commands)
responses.pop(0)
return responses
def execute(self, commands, **kwargs):
if not self.connected:
self.connect()
return self.connection.send(commands, **kwargs)
def disconnect(self):
self.connection.close()
self._connected = False
def parse_config(self, cfg):
return parse(cfg, indent=1)
def get_config(self):
if self.filter:
cmd = 'show running-config %s ' % self.filter
else:
cmd = self.params['show_command']
if self.params.get('include_defaults'):
cmd += ' all'
return self.execute(cmd)[0]
def get_module(**kwargs):
"""Return instance of NetworkModule
"""
argument_spec = NET_COMMON_ARGS.copy()
if kwargs.get('argument_spec'):
argument_spec.update(kwargs['argument_spec'])
kwargs['argument_spec'] = argument_spec
module = NetworkModule(**kwargs)
# HAS_PARAMIKO is set by module_utils/shell.py
if not HAS_PARAMIKO:
module.fail_json(msg='paramiko is required but does not appear to be installed')
return module

View file

@ -0,0 +1,26 @@
#
# Copyright 2015 Peter Sprygada <psprygada@ansible.com>
#
# 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
from ansible.plugins.action import ActionBase
from ansible.plugins.action.net_template import ActionModule as NetActionModule
class ActionModule(NetActionModule, ActionBase):
pass

View file

@ -0,0 +1,113 @@
#
# (c) 2016, Peter Sprygada <psprygada@ansible.com>
# (c) 2016, Patrick Ogenstad <@ogenstad>
#
# 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/>.
class ModuleDocFragment(object):
# Standard files documentation fragment
DOCUMENTATION = """
options:
host:
description:
- Specifies the DNS host name or address for connecting to the remote
device over the specified transport. The value of host is used as
the destination address for the transport.
required: true
port:
description:
- Specifies the port to use when buiding the connection to the remote
device. The port value will default to the well known SSH port
of 22
required: false
default: 22
username:
description:
- Configures the usename to use to authenticate the connection to
the remote device. The value of I(username) is used to authenticate
the SSH session. If the value is not specified in the task, the
value of environment variable ANSIBLE_NET_USERNAME will be used instead.
required: false
password:
description:
- Specifies the password to use to authenticate the connection to
the remote device. The value of I(password) is used to authenticate
the SSH session. If the value is not specified in the task, the
value of environment variable ANSIBLE_NET_PASSWORD will be used instead.
required: false
default: null
ssh_keyfile:
description:
- Specifies the SSH key to use to authenticate the connection to
the remote device. The value of I(ssh_keyfile) is the path to the
key used to authenticate the SSH session. If the value is not specified
in the task, the value of environment variable ANSIBLE_NET_SSH_KEYFILE
will be used instead.
required: false
authorize:
description:
- Instructs the module to enter priviledged mode on the remote device
before sending any commands. If not specified, the device will
attempt to excecute all commands in non-priviledged mode. If the value
is not specified in the task, the value of environment variable
ANSIBLE_NET_AUTHORIZE will be used instead.
required: false
default: no
choices: ['yes', 'no']
auth_pass:
description:
- Specifies the password to use if required to enter privileged mode
on the remote device. If I(authorize) is false, then this argument
does nothing. If the value is not specified in the task, the value of
environment variable ANSIBLE_NET_AUTH_PASS will be used instead.
required: false
default: none
timeout:
description:
- Specifies idle timeout for the connection. Useful if the console
freezes before continuing. For example when saving configurations.
required: false
default: 10
provider:
description:
- Convience method that allows all M(ios) arguments to be passed as
a dict object. All constraints (required, choices, etc) must be
met either by individual arguments or values in this dict.
required: false
default: null
show_command:
description:
- Specifies which command will be used to get the current configuration.
By default the 'show running-config' command will be used, this command
masks some passwords. For example ike passwords for VPN. If you need to
match against masked passwords use 'more system:running-config'.
Note that the 'more system:running-config' only works in the system
context if you are running the ASA in multiple context mode.
before sending any commands. If not specified, the device will
required: false
default: show running-config
choices: ['show running-config', 'more system:running-config']
context:
description:
- Specifies which context to target if you are running in the ASA in
multiple context mode. Defaults to the current context you login to.
required: false
default: null
"""