Merge pull request #13777 from privateip/shared_module_ios

updates the ios shared module with new shell
This commit is contained in:
Peter Sprygada 2016-01-10 14:28:00 -05:00
commit a0a4edd494
2 changed files with 156 additions and 136 deletions

View file

@ -16,165 +16,118 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>. # 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 * NET_COMMON_ARGS = dict(
from ansible.module_utils.ssh import * host=dict(required=True),
from ansible.module_utils.ios import * port=dict(default=22, type='int'),
username=dict(required=True),
This module provides the following common argument spec for creating password=dict(no_log=True),
ios connections: authorize=dict(default=False, type='bool'),
auth_pass=dict(no_log=True),
* 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()
) )
def to_list(val):
def ios_module(**kwargs): if isinstance(val, (list, tuple)):
"""Append the common args to the argument_spec return list(val)
""" elif val is not None:
spec = kwargs.get('argument_spec') or dict() return [val]
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]
else: 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.connection = None
self._config = None
def connect(self, host, username, password, **kwargs): @property
port = kwargs.get('port') or 22 def config(self):
timeout = kwargs.get('timeout') or 10 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) if self.params['authorize']:
self.connection.errors.extend(IOS_ERRORS_RE) self.connection.authorize()
self.connection.open(host, port=port, username=username, except Exception, exc:
password=password, timeout=timeout) self.fail_json(msg=exc.message)
def authorize(self, passwd=None):
command = Command('enable', prompt=IOS_PASSWD_RE, response=passwd)
self.send(command)
def configure(self, commands): def configure(self, commands):
commands = to_list(commands) commands = to_list(commands)
commands.insert(0, 'configure terminal') commands.insert(0, 'configure terminal')
commands.append('end') responses = self.execute(commands)
responses.pop(0)
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)
return responses return responses
def ios_connection(module): def execute(self, commands, **kwargs):
"""Creates a connection to an IOS device based on the module arguments 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'] argument_spec = NET_COMMON_ARGS.copy()
password = module.params['password'] 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: # HAS_PARAMIKO is set by module_utils/shell.py
shell = IosShell() if not HAS_PARAMIKO:
shell.connect(host, port=port, username=username, password=password, module.fail_json(msg='paramiko is required but does not appear to be installed')
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)
if module.params['enable_mode']: # copy in values from local action.
shell.authorize(module.params['enable_password']) params = json_dict_unicode_to_bytes(json.loads(MODULE_COMPLEX_ARGS))
for key, value in params.iteritems():
return shell module.params[key] = value
module.connect()
return module

View 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
"""