From d1dacfb3cabad190429b287d1d8256e46d10bcd3 Mon Sep 17 00:00:00 2001 From: Peter Sprygada Date: Thu, 7 Jan 2016 23:27:56 -0500 Subject: [PATCH] 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. --- lib/ansible/module_utils/ios.py | 213 +++++++----------- .../utils/module_docs_fragments/ios.py | 67 ++++++ 2 files changed, 150 insertions(+), 130 deletions(-) create mode 100644 lib/ansible/utils/module_docs_fragments/ios.py diff --git a/lib/ansible/module_utils/ios.py b/lib/ansible/module_utils/ios.py index 085b68dcd28..550a2de6d53 100644 --- a/lib/ansible/module_utils/ios.py +++ b/lib/ansible/module_utils/ios.py @@ -16,165 +16,118 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # -""" -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. ** - -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_PASSWD_RE = re.compile(r"[\r\n]?password: $", re.I) + +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 to_list(val): + if isinstance(val, (list, tuple)): + return list(val) + elif val is not None: + return [val] + else: + return list() -def ios_module(**kwargs): - """Append the common args to the argument_spec - """ - spec = kwargs.get('argument_spec') or dict() +class Cli(object): - 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 + def __init__(self, module): + self.module = module + self.shell = None - module = AnsibleModule(**kwargs) + def connect(self, **kwargs): + host = self.module.params['host'] + port = self.module.params['port'] or 22 - device = module.params.get('device') or dict() - for key, value in device.iteritems(): - if key in IOS_COMMON_ARGS: - module.params[key] = value + username = self.module.params['username'] + password = self.module.params['password'] - params = json_dict_unicode_to_bytes(json.loads(MODULE_COMPLEX_ARGS)) - for key, value in params.iteritems(): - if key != 'device': - module.params[key] = value + self.shell = Shell() + self.shell.open(host, port=port, username=username, password=password) - return module + def authorize(self): + passwd = self.module.params['auth_pass'] + self.send(Command('enable', prompt=NET_PASSWD_RE, response=passwd)) -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: - return [] + def send(self, commands): + return self.shell.send(commands) -class IosShell(object): +class IosModule(AnsibleModule): - def __init__(self): + 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') + responses = self.execute(commands) + responses.pop(0) + return responses - resp = self.send(commands) - resp.pop(0) - resp.pop() + def execute(self, commands, **kwargs): + return self.connection.send(commands) - return resp + def disconnect(self): + self.connection.close() - def send(self, commands): - responses = list() - for cmd in to_list(commands): - response = self.connection.send(cmd) - responses.append(response) - return responses + def parse_config(self, cfg): + return parse(cfg, indent=1) -def ios_connection(module): - """Creates a connection to an IOS device based on the module arguments - """ - host = module.params['host'] - port = module.params['port'] + def get_config(self): + cmd = 'show running-config' + if self.params['include_defaults']: + cmd += ' all' + return self.execute(cmd)[0] - username = module.params['username'] - password = module.params['password'] +def get_module(**kwargs): + """Return instance of IosModule + """ - timeout = module.params['connect_timeout'] + 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 - 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) + module = IosModule(**kwargs) - if module.params['enable_mode']: - shell.authorize(module.params['enable_password']) + # 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 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 diff --git a/lib/ansible/utils/module_docs_fragments/ios.py b/lib/ansible/utils/module_docs_fragments/ios.py new file mode 100644 index 00000000000..5f07bbfde76 --- /dev/null +++ b/lib/ansible/utils/module_docs_fragments/ios.py @@ -0,0 +1,67 @@ +# +# (c) 2015, Peter Sprygada +# +# 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 . + + +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 + +"""