From 637bbdadfa6cc73c7bca4e30836b120f617cee0a Mon Sep 17 00:00:00 2001 From: Peter Sprygada Date: Wed, 22 Jun 2016 09:32:34 -0500 Subject: [PATCH] add new features to ios shared module * add load_config() for loading a set of configuration commands * add load_candidate() function for loading a candidate config * updates shared module to provide NetworKModule instead of get_module * fixes Cli transport implementation for 2.2 refactor * updates ios documentation fragments with new options --- lib/ansible/module_utils/ios.py | 176 +++++++++++------- .../utils/module_docs_fragments/ios.py | 30 ++- 2 files changed, 138 insertions(+), 68 deletions(-) diff --git a/lib/ansible/module_utils/ios.py b/lib/ansible/module_utils/ios.py index dd853234b89..d7913b36d0d 100644 --- a/lib/ansible/module_utils/ios.py +++ b/lib/ansible/module_utils/ios.py @@ -17,6 +17,7 @@ # along with Ansible. If not, see . # +import urlparse import re from ansible.module_utils.basic import json, get_exception @@ -26,9 +27,6 @@ from ansible.module_utils.network import add_argument, register_transport, to_li from ansible.module_utils.netcfg import NetworkConfig from ansible.module_utils.urls import fetch_url, url_argument_spec -# temporary fix until modules are update. to be removed before 2.2 final -from ansible.module_utils.network import get_module - add_argument('use_ssl', dict(default=True, type='bool')) add_argument('validate_certs', dict(default=True, type='bool')) @@ -41,12 +39,113 @@ def argument_spec(): ios_argument_spec = argument_spec() def get_config(module, include_defaults=False): - config = module.params['running_config'] - if not config and not include_defaults: - config = module.config.get_config() + contents = module.params['running_config'] + if not contents: + if not include_defaults: + contents = module.config.get_config() + else: + contents = module.cli('show running-config all')[0] + module.params['running_config'] = contents + return NetworkConfig(indent=1, contents=contents) + +def load_candidate(module, candidate, nodiff=False): + if nodiff: + updates = str(candidate) else: - config = module.cli('show running-config all')[0] - return NetworkConfig(indent=1, contents=config) + config = get_config(module) + updates = candidate.difference(config) + + result = dict(changed=False, saved=False) + + if updates: + if not module.check_mode: + module.config(updates) + result['changed'] = True + + if not module.check_mode and module.params['save_config'] is True: + module.config.save_config() + result['saved'] = True + + result['updates'] = updates + return result + +def load_config(module, commands, nodiff=False): + contents = '\n'.join(to_list(commands)) + candidate = NetworkConfig(contents=contents, indent=1) + return load_candidate(module, candidate, nodiff) + + +class Cli(NetCli): + + NET_PASSWD_RE = re.compile(r"[\r\n]?password: $", re.I) + + CLI_PROMPTS_RE = [ + re.compile(r"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"), + re.compile(r"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$") + ] + + CLI_ERRORS_RE = [ + re.compile(r"% ?Error"), + 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+"), + ] + + def connect(self, params, **kwargs): + super(Cli, self).connect(params, kickstart=False, **kwargs) + self.shell.send('terminal length 0') + self._connected = True + + def authorize(self, params, **kwargs): + passwd = params['auth_pass'] + self.run_commands( + Command('enable', prompt=self.NET_PASSWD_RE, response=passwd) + ) + + def disconnect(self): + self._connected = False + + ### Cli methods ### + + def run_commands(self, commands, **kwargs): + commands = to_list(commands) + return self.execute([str(c) for c in commands]) + + ### Config methods ### + + def configure(self, commands, **kwargs): + cmds = ['configure terminal'] + cmds.extend(to_list(commands)) + cmds.append('end') + responses = self.execute(cmds) + responses.pop(0) + return responses + + def get_config(self, include_defaults=False, **kwargs): + cmd = 'show running-config' + if include_defaults: + cmd += ' all' + return self.run_commands(cmd)[0] + + def load_config(self, commands, commit=False, **kwargs): + raise NotImplementedError + + def replace_config(self, commands, **kwargs): + raise NotImplementedError + + def commit_config(self, **kwargs): + raise NotImplementedError + + def abort_config(self, **kwargs): + raise NotImplementedError + + def save_config(self): + self.execute(['copy running-config startup-config']) + +Cli = register_transport('cli', default=True)(Cli) class Restconf(object): @@ -93,6 +192,7 @@ class Restconf(object): def authorize(self): pass + ### REST methods ### def request(self, method, path, data=None, headers=None): @@ -177,64 +277,6 @@ class Restconf(object): def save_config(self): self.put('/api/v1/global/save-config') -Restconf = register_transport('restconf')(Restconf) - - -class Cli(NetCli): - CLI_PROMPTS_RE = [ - re.compile(r"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"), - re.compile(r"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$") - ] - - CLI_ERRORS_RE = [ - re.compile(r"% ?Error"), - 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+"), - ] - - NET_PASSWD_RE = re.compile(r"[\r\n]?password: $", re.I) - - def connect(self, params, **kwargs): - super(Cli, self).connect(params, kickstart=False, **kwargs) - self.shell.send('terminal length 0') - ### implementation of network.Cli ### - - def configure(self, commands, **kwargs): - cmds = ['configure terminal'] - cmds.extend(to_list(commands)) - cmds.append('end') - - responses = self.execute(cmds) - return responses[1:-1] - - def get_config(self, params, **kwargs): - cmd = 'show running-config' - if params.get('include_defaults'): - cmd += ' all' - return self.execute(cmd)[0] - - def load_config(self, commands, commit=False, **kwargs): - raise NotImplementedError - - def replace_config(self, commands, **kwargs): - raise NotImplementedError - - def commit_config(self, **kwargs): - raise NotImplementedError - - def abort_config(self, **kwargs): - raise NotImplementedError - - def save_config(self): - self.execute(['copy running-config startup-config']) +Restconf = register_transport('restconf')(Restconf) - def run_commands(self, commands): - cmds = to_list(commands) - responses = self.execute(cmds) - return responses -Cli = register_transport('cli', default=True)(Cli) diff --git a/lib/ansible/utils/module_docs_fragments/ios.py b/lib/ansible/utils/module_docs_fragments/ios.py index 514e9d1ab1a..2076168cbf3 100644 --- a/lib/ansible/utils/module_docs_fragments/ios.py +++ b/lib/ansible/utils/module_docs_fragments/ios.py @@ -89,5 +89,33 @@ options: met either by individual arguments or values in this dict. required: false default: null - + force: + description: + - The force argument instructs the module to not consider the + current devices running-config. When set to true, this will + cause the module to push the contents of I(src) into the device + without first checking if already configured. + required: false + default: false + choices: ['yes', 'no'] + running_config: + description: + - The module, by default, will connect to the remote device and + retrieve the current running-config to use as a base for comparing + against the contents of source. There are times when it is not + desirable to have the task get the current running-config for + every task in a playbook. The I(config) argument allows the + implementer to pass in the configuruation to use as the base + config for comparision. + required: false + default: null + aliases: ['config'] + save_config: + description: + - Specifies the current C(running-config) should be saved to + non-volatile memory so that configuration changes are + persistent if the device is restarted. + required: false + default: null + aliases: ['save'] """