updates the ios shared module with new shell
This update refactor the ios shared module to use the new shell shared library instead of issh and cli. It also adds the ios documentation fragment to be used when building ios based modules.
This commit is contained in:
parent
e6ee59fafe
commit
d1dacfb3ca
2 changed files with 156 additions and 136 deletions
|
@ -16,165 +16,118 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
"""
|
||||
Adds shared module support for connecting to and configuring Cisco
|
||||
IOS devices. This shared module builds on module_utils/ssh.py and
|
||||
implements the Shell object.
|
||||
|
||||
** Note: The order of the import statements does matter. **
|
||||
NET_PASSWD_RE = re.compile(r"[\r\n]?password: $", re.I)
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.ssh import *
|
||||
from ansible.module_utils.ios import *
|
||||
|
||||
This module provides the following common argument spec for creating
|
||||
ios connections:
|
||||
|
||||
* enable_mode (bool) - Forces the shell connection into IOS enable mode
|
||||
|
||||
* enable_password (str) - Configures the IOS enable mode password to be
|
||||
send to the device to authorize the session
|
||||
|
||||
* device (dict) - Accepts the set of configuration parameters as a
|
||||
dict object
|
||||
|
||||
Note: These shared arguments are in addition to the arguments provided by
|
||||
the module_utils/ssh.py shared module
|
||||
|
||||
"""
|
||||
import socket
|
||||
|
||||
IOS_PROMPTS_RE = [
|
||||
re.compile(r'[\r\n]?[a-zA-Z]{1}[a-zA-Z0-9-]*[>|#](?:\s*)$'),
|
||||
re.compile(r'[\r\n]?[a-zA-Z]{1}[a-zA-Z0-9-]*\(.+\)#$'),
|
||||
re.compile(r'\x1b.*$')
|
||||
]
|
||||
|
||||
IOS_ERRORS_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+"),
|
||||
]
|
||||
|
||||
IOS_PASSWD_RE = re.compile(r"[\r\n]?password: $", re.I)
|
||||
|
||||
IOS_COMMON_ARGS = dict(
|
||||
host=dict(),
|
||||
port=dict(type='int', default=22),
|
||||
username=dict(),
|
||||
password=dict(),
|
||||
enable_mode=dict(default=False, type='bool'),
|
||||
enable_password=dict(),
|
||||
connect_timeout=dict(type='int', default=10),
|
||||
device=dict()
|
||||
NET_COMMON_ARGS = dict(
|
||||
host=dict(required=True),
|
||||
port=dict(default=22, type='int'),
|
||||
username=dict(required=True),
|
||||
password=dict(no_log=True),
|
||||
authorize=dict(default=False, type='bool'),
|
||||
auth_pass=dict(no_log=True),
|
||||
)
|
||||
|
||||
|
||||
def ios_module(**kwargs):
|
||||
"""Append the common args to the argument_spec
|
||||
"""
|
||||
spec = kwargs.get('argument_spec') or dict()
|
||||
|
||||
argument_spec = shell_argument_spec()
|
||||
argument_spec.update(IOS_COMMON_ARGS)
|
||||
if kwargs.get('argument_spec'):
|
||||
argument_spec.update(kwargs['argument_spec'])
|
||||
kwargs['argument_spec'] = argument_spec
|
||||
|
||||
module = AnsibleModule(**kwargs)
|
||||
|
||||
device = module.params.get('device') or dict()
|
||||
for key, value in device.iteritems():
|
||||
if key in IOS_COMMON_ARGS:
|
||||
module.params[key] = value
|
||||
|
||||
params = json_dict_unicode_to_bytes(json.loads(MODULE_COMPLEX_ARGS))
|
||||
for key, value in params.iteritems():
|
||||
if key != 'device':
|
||||
module.params[key] = value
|
||||
|
||||
return module
|
||||
|
||||
def to_list(arg):
|
||||
"""Try to force the arg to a list object
|
||||
"""
|
||||
if isinstance(arg, (list, tuple)):
|
||||
return list(arg)
|
||||
elif arg is not None:
|
||||
return [arg]
|
||||
def to_list(val):
|
||||
if isinstance(val, (list, tuple)):
|
||||
return list(val)
|
||||
elif val is not None:
|
||||
return [val]
|
||||
else:
|
||||
return []
|
||||
return list()
|
||||
|
||||
class IosShell(object):
|
||||
class Cli(object):
|
||||
|
||||
def __init__(self):
|
||||
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']
|
||||
|
||||
self.shell = Shell()
|
||||
self.shell.open(host, port=port, username=username, password=password)
|
||||
|
||||
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 IosModule(AnsibleModule):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(IosModule, self).__init__(*args, **kwargs)
|
||||
self.connection = None
|
||||
self._config = None
|
||||
|
||||
def connect(self, host, username, password, **kwargs):
|
||||
port = kwargs.get('port') or 22
|
||||
timeout = kwargs.get('timeout') or 10
|
||||
@property
|
||||
def config(self):
|
||||
if not self._config:
|
||||
self._config = self.get_config()
|
||||
return self._config
|
||||
|
||||
self.connection = Shell()
|
||||
def connect(self):
|
||||
try:
|
||||
self.connection = Cli(self)
|
||||
self.connection.connect()
|
||||
self.execute('terminal length 0')
|
||||
|
||||
self.connection.prompts.extend(IOS_PROMPTS_RE)
|
||||
self.connection.errors.extend(IOS_ERRORS_RE)
|
||||
if self.params['authorize']:
|
||||
self.connection.authorize()
|
||||
|
||||
self.connection.open(host, port=port, username=username,
|
||||
password=password, timeout=timeout)
|
||||
|
||||
def authorize(self, passwd=None):
|
||||
command = Command('enable', prompt=IOS_PASSWD_RE, response=passwd)
|
||||
self.send(command)
|
||||
except Exception, exc:
|
||||
self.fail_json(msg=exc.message)
|
||||
|
||||
def configure(self, commands):
|
||||
commands = to_list(commands)
|
||||
|
||||
commands.insert(0, 'configure terminal')
|
||||
commands.append('end')
|
||||
|
||||
resp = self.send(commands)
|
||||
resp.pop(0)
|
||||
resp.pop()
|
||||
|
||||
return resp
|
||||
|
||||
def send(self, commands):
|
||||
responses = list()
|
||||
for cmd in to_list(commands):
|
||||
response = self.connection.send(cmd)
|
||||
responses.append(response)
|
||||
responses = self.execute(commands)
|
||||
responses.pop(0)
|
||||
return responses
|
||||
|
||||
def ios_connection(module):
|
||||
"""Creates a connection to an IOS device based on the module arguments
|
||||
def execute(self, commands, **kwargs):
|
||||
return self.connection.send(commands)
|
||||
|
||||
def disconnect(self):
|
||||
self.connection.close()
|
||||
|
||||
def parse_config(self, cfg):
|
||||
return parse(cfg, indent=1)
|
||||
|
||||
def get_config(self):
|
||||
cmd = 'show running-config'
|
||||
if self.params['include_defaults']:
|
||||
cmd += ' all'
|
||||
return self.execute(cmd)[0]
|
||||
|
||||
def get_module(**kwargs):
|
||||
"""Return instance of IosModule
|
||||
"""
|
||||
host = module.params['host']
|
||||
port = module.params['port']
|
||||
|
||||
username = module.params['username']
|
||||
password = module.params['password']
|
||||
argument_spec = NET_COMMON_ARGS.copy()
|
||||
if kwargs.get('argument_spec'):
|
||||
argument_spec.update(kwargs['argument_spec'])
|
||||
kwargs['argument_spec'] = argument_spec
|
||||
kwargs['check_invalid_arguments'] = False
|
||||
|
||||
timeout = module.params['connect_timeout']
|
||||
module = IosModule(**kwargs)
|
||||
|
||||
try:
|
||||
shell = IosShell()
|
||||
shell.connect(host, port=port, username=username, password=password,
|
||||
timeout=timeout)
|
||||
shell.send('terminal length 0')
|
||||
except paramiko.ssh_exception.AuthenticationException, exc:
|
||||
module.fail_json(msg=exc.message)
|
||||
except socket.error, exc:
|
||||
module.fail_json(msg=exc.strerror, errno=exc.errno)
|
||||
# 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')
|
||||
|
||||
if module.params['enable_mode']:
|
||||
shell.authorize(module.params['enable_password'])
|
||||
|
||||
return shell
|
||||
# copy in values from local action.
|
||||
params = json_dict_unicode_to_bytes(json.loads(MODULE_COMPLEX_ARGS))
|
||||
for key, value in params.iteritems():
|
||||
module.params[key] = value
|
||||
|
||||
module.connect()
|
||||
|
||||
return module
|
||||
|
||||
|
|
67
lib/ansible/utils/module_docs_fragments/ios.py
Normal file
67
lib/ansible/utils/module_docs_fragments/ios.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
#
|
||||
# (c) 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/>.
|
||||
|
||||
|
||||
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
|
||||
required: true
|
||||
password:
|
||||
description:
|
||||
- Specifies the password to use when authentication the connection to
|
||||
the remote device. The value of I(password) is used to authenticate
|
||||
the SSH session
|
||||
required: false
|
||||
default: null
|
||||
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.
|
||||
required: false
|
||||
default: false
|
||||
choices: BOOLEANS
|
||||
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
|
||||
required: false
|
||||
default: none
|
||||
|
||||
"""
|
Loading…
Reference in a new issue