diff --git a/lib/ansible/module_utils/ios.py b/lib/ansible/module_utils/ios.py new file mode 100644 index 0000000000..dc46a860c6 --- /dev/null +++ b/lib/ansible/module_utils/ios.py @@ -0,0 +1,195 @@ +# +# (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 . +# +""" +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() +) + + +def ios_module(**kwargs): + """Append the common args to the argument_spec + """ + spec = kwargs.get('argument_spec') or dict() + + argument_spec = url_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: + return [] + +class IosShell(object): + + def __init__(self): + self.connection = None + + def connect(self, host, username, password, **kwargs): + port = kwargs.get('port') or 22 + timeout = kwargs.get('timeout') or 10 + + self.connection = Shell() + + self.connection.prompts.extend(IOS_PROMPTS_RE) + self.connection.errors.extend(IOS_ERRORS_RE) + + 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) + + 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) + return responses + +def ios_from_args(module): + """Extracts the set of argumetns to build a valid IOS connection + """ + params = dict() + for arg, attrs in IOS_COMMON_ARGS.iteritems(): + if module.params['device']: + params[arg] = module.params['device'].get(arg) + if arg not in params or module.params[arg]: + params[arg] = module.params[arg] + if params[arg] is None: + if attrs.get('required'): + module.fail_json(msg='argument %s is required' % arg) + params[arg] = attrs.get('default') + return params + +def ios_connection(module): + """Creates a connection to an IOS device based on the module arguments + """ + host = module.params['host'] + port = module.params['port'] + + username = module.params['username'] + password = module.params['password'] + + timeout = module.params['connect_timeout'] + + try: + shell = IosShell() + shell.connect(host, port=port, username=username, password=password, + timeout=timeout) + 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) + + shell.send('terminal length 0') + + if module.params['enable_mode']: + shell.authorize(module.params['enable_password']) + + return shell + +