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