From 77922f82e2cf726cb269bf34f1f735072812b7e2 Mon Sep 17 00:00:00 2001 From: Peter Sprygada Date: Thu, 23 Jun 2016 12:27:33 -0700 Subject: [PATCH] initial commit of vyos shared module with Cli transport This adds support for the VyOS network operating system using the Cli transport. This module will simplify building VyOS based modules in Ansible --- lib/ansible/module_utils/vyos.py | 154 +++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 lib/ansible/module_utils/vyos.py diff --git a/lib/ansible/module_utils/vyos.py b/lib/ansible/module_utils/vyos.py new file mode 100644 index 0000000000..1ae74b0900 --- /dev/null +++ b/lib/ansible/module_utils/vyos.py @@ -0,0 +1,154 @@ +# +# (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 . +# + +import re + +from ansible.module_utils.network import NetworkError, get_module, get_exception +from ansible.module_utils.network import register_transport, to_list +from ansible.module_utils.network import Command, NetCli +from ansible.module_utils.shell import Shell, ShellError, HAS_PARAMIKO + +DEFAULT_COMMENT = 'configured by vyos_config' + +def argument_spec(): + return dict( + config=dict(), + comment=dict(default=DEFAULT_COMMENT), + save=dict(type='bool') + ) +vyos_argument_spec = argument_spec() + +get_config = lambda x: x.params['config'] or x.config.get_config() + +def diff_config(candidate, config): + updates = set() + config = [str(c).replace("'", '') for c in config] + + for line in candidate: + item = str(line).replace("'", '') + + if not item.startswith('set') and not item.startswith('delete'): + raise ValueError('line must start with either `set` or `delete`') + + elif item.startswith('set') and item not in config: + updates.add(line) + + elif item.startswith('delete'): + if not config: + updates.add(line) + else: + item = re.sub(r'delete', 'set', item) + for entry in config: + if entry.startswith(item): + updates.add(line) + + return list(updates) + +def load_config(module, candidate): + config = get_config(module).split('\n') + updates = diff_config(candidate, config) + + comment = module.params['comment'] + save = module.params['save'] + + result = dict(changed=False) + + if updates: + diff = module.config.load_config(updates) + if diff: + result['diff'] = dict(prepared=diff) + + result['changed'] = True + result['updates'] = updates + + if not module.check_mode: + module.config.commit_config(comment=comment) + if save: + module.config.save_config() + else: + module.config.abort_config() + + # exit from config mode + module.cli('exit') + + return result + + +class Cli(NetCli): + + CLI_PROMPTS_RE = [ + re.compile(r"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"), + re.compile(r"\@[\w\-\.]+:\S+?[>#\$] ?$") + ] + + CLI_ERRORS_RE = [ + re.compile(r"\n\s*Invalid command:"), + re.compile(r"\nCommit failed"), + re.compile(r"\n\s+Set failed"), + ] + + def connect(self, params, **kwargs): + super(Cli, self).connect(params, kickstart=False, **kwargs) + self.shell.send('set terminal length 0') + self._connected = True + + ### Cli methods ### + + def run_commands(self, commands, **kwargs): + commands = to_list(commands) + response = self.execute([str(c) for c in commands]) + return response + + ### Config methods ### + + def configure(self, commands, commit=True, **kwargs): + """Called by Config.__call__ + """ + cmds = ['configure'] + cmds.extend(to_list(commands)) + response = self.execute(cmds) + if commit: + self.commit_config() + return response + + def load_config(self, commands): + self.configure(commands, commit=False) + diff = None + if not self.execute('compare')[0].startswith('No changes'): + diff = self.execute(['show'])[0] + return diff + + def get_config(self): + return self.execute(['show configuration commands'])[0] + + def commit_config(self, confirm=0, comment=None): + if confirm > 0: + cmd = 'commit-confirm %s' % confirm + else: + cmd = 'commit' + if comment: + cmd += ' comment "%s"' % comment + self.execute([cmd]) + + def abort_config(self): + self.execute(['discard']) + + def save_config(self): + self.execute(['save']) +Cli = register_transport('cli', default=True)(Cli)