From 543ec0f14e3b62ffbe7dc5ec1703423319c768f9 Mon Sep 17 00:00:00 2001 From: Peter Sprygada Date: Wed, 10 Aug 2016 15:59:05 -0400 Subject: [PATCH] update nxos_config with new arguments * add src argument to provide path to config file * add new choice to match used to ignore current running config * add update argument with choices merge or check * add backup argument to backup current running config to control host * add defaults argument to control collection of config with or without defaults * add save argument to save current running config to startup config * add state argument to control state of config file * deprecated force argument, use match=none instead --- .../modules/network/nxos/nxos_config.py | 301 ++++++++++++++---- 1 file changed, 233 insertions(+), 68 deletions(-) diff --git a/lib/ansible/modules/network/nxos/nxos_config.py b/lib/ansible/modules/network/nxos/nxos_config.py index 4f6c0b66c27..b7e40dd1edc 100644 --- a/lib/ansible/modules/network/nxos/nxos_config.py +++ b/lib/ansible/modules/network/nxos/nxos_config.py @@ -37,7 +37,8 @@ options: in the device running-config. Be sure to note the configuration command syntax as some commands are automatically modified by the device config parser. - required: true + required: false + default: null parents: description: - The ordered set of parents that uniquely identify the section @@ -46,6 +47,17 @@ options: level or global commands. required: false default: null + src: + description: + - The I(src) argument provides a path to the configuration file + to load into the remote system. The path can either be a full + system path to the configuration file if the value starts with / + or relative to the root of the implemented role or playbook. + This arugment is mutually exclusive with the I(lines) and + I(parents) arguments. + required: false + default: null + version_added: "2.2" before: description: - The ordered set of commands to push on to the command stack if @@ -71,9 +83,12 @@ options: match is set to I(strict), command lines are matched with respect to position. Finally if match is set to I(exact), command lines must be an equal match. + - Version 2.2 added a new choice I(none). When match is set to + none, the configure is loaded into the remote device without + consulting the configuration. required: false default: line - choices: ['line', 'strict', 'exact'] + choices: ['line', 'strict', 'exact', 'none'] replace: description: - Instructs the module on the way to perform the configuration @@ -91,9 +106,25 @@ options: 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. + - Note this argument should be considered deprecated. To achieve + the equivalient, set the match argument to none. This argument + will be removed in a future release. required: false default: false choices: [ "true", "false" ] + update: + description: + - The I(update) argument controls how the configuration statements + are processed on the remote device. Valid choices for the I(update) + argument are I(merge) and I(check). When the argument is set to + I(merge), the configuration changes are merged with the current + device running configuration. When the argument is set to I(check) + the configuration updates are determined but not actually configured + on the remote device. + required: false + default: merge + choices: ['merge', 'check'] + version_added: "2.2" config: description: - The module, by default, will connect to the remote device and @@ -105,12 +136,55 @@ options: config for comparison. required: false default: null + defaults: + description: + - The I(defaults) argument will influence how the running-config + is collected from the device. When the value is set to true, + the command used to collect the running-config is append with + the all keyword. When the value is set to false, the command + is issued without the all keyword + required: false + default: false + version_added: "2.2" + save: + description: + - The I(save) argument will instruct the module to save the + running-config to startup-config. This operation is performed + after any changes are made to the current running config. If + no changes are made, the configuration is still saved to the + startup config. This option will always cause the module to + return changed. + required: false + default: false + version_added: "2.2" + state: + description: + - The I(state) argument specifies the state of the config + file on the device. When set to present, the configuration + is updated based on the values of the module. When the value + is set to absent, the device startup config is erased. + required: true + default: present + choices: ['present', 'absent'] + version_added: "2.2" """ + EXAMPLES = """ -- nxos_config: - lines: ['hostname {{ inventory_hostname }}'] - force: yes +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. +vars: + cli: + host: "{{ inventory_hostname }}" + username: admin + password: admin + transport: cli + +- name: configure top level configuration and save it + nxos_config: + lines: hostname {{ inventory_hostname }} + save: yes + provider: "{{ cli }}" - nxos_config: lines: @@ -119,9 +193,10 @@ EXAMPLES = """ - 30 permit ip 3.3.3.3/32 any log - 40 permit ip 4.4.4.4/32 any log - 50 permit ip 5.5.5.5/32 any log - parents: ['ip access-list test'] - before: ['no ip access-list test'] + parents: ip access-list test + before: no ip access-list test match: exact + provider: "{{ cli }}" - nxos_config: lines: @@ -129,16 +204,10 @@ EXAMPLES = """ - 20 permit ip 2.2.2.2/32 any log - 30 permit ip 3.3.3.3/32 any log - 40 permit ip 4.4.4.4/32 any log - parents: ['ip access-list test'] - before: ['no ip access-list test'] - replace: block - -- nxos_config: - lines: "{{lookup('file', 'datcenter1.txt')}}" - parents: ['ip access-list test'] - before: ['no ip access-list test'] + parents: ip access-list test + before: no ip access-list test replace: block - + provider: "{{ cli }}" """ RETURN = """ @@ -147,83 +216,179 @@ updates: returned: always type: list sample: ['...', '...'] - -responses: - description: The set of responses from issuing the commands on the device - retured: when not check_mode - type: list - sample: ['...', '...'] """ +import time + +from ansible.module_utils.netcfg import NetworkConfig, dumps +from ansible.module_utils.nxos import NetworkModule, NetworkError +from ansible.module_utils.basic import get_exception + +def invoke(name, *args, **kwargs): + func = globals().get(name) + if func: + return func(*args, **kwargs) + +def check_args(module, warnings): + if module.params['save'] and module.check_mode: + warnings.append('will not save configuration due to checkmode') + if module.params['parents'] and module.params['src']: + warnings.append('ignoring parents argument when src specified') + if module.params['force']: + warnings.append('The force argument is deprecated, please use ' + 'match=none instead. This argument will be ' + 'removed in the future') + +def get_candidate(module): + candidate = NetworkConfig(indent=2) + if module.params['src']: + candidate.load(module.params['src']) + elif module.params['lines']: + parents = module.params['parents'] or list() + candidate.add(module.params['lines'], parents=parents) + return candidate + +def get_config(module, result): + defaults = module.params['defaults'] + if defaults is True: + key = '__configall__' + else: + key = '__config__' -def get_config(module): - config = module.params['config'] or dict() - if not config and not module.params['force']: - config = module.config - return config + contents = module.params['config'] or result.get(key) + if not contents: + contents = module.config.get_config(include_defaults=defaults) + result[key] = contents + + return NetworkConfig(indent=1, contents=contents) + +def backup_config(module, result): + if '__config__' not in result: + result['__config__'] = module.config.get_config() + result['__backup__'] = result['__config__'] + +def load_config(module, commands, result): + if not module.check_mode: + checkpoint = 'ansible_%s' % int(time.time()) + module.cli(['checkpoint %s' % checkpoint], output='text') + result['__checkpoint__'] = checkpoint + module.config.load_config(commands) + result['changed'] = True + +def load_checkpoint(module, result): + try: + checkpoint = result['__checkpoint__'] + module.cli(['rollback running-config checkpoint %s' % checkpoint, + 'no checkpoint %s' % checkpoint], output='text') + except KeyError: + module.fail_json(msg='unable to rollback, checkpoint not found') + except NetworkError: + exc = get_exception() + msg = 'unable to rollback configuration' + module.fail_json(msg=msg, checkpoint=checkpoint, **exc.kwargs) + +def present(module, result): + match = module.params['match'] + replace = module.params['replace'] + update = module.params['update'] + + candidate = get_candidate(module) + + if match != 'none': + config = get_config(module, result) + configobjs = candidate.difference(config, match=match, replace=replace) + else: + config = None + configobjs = candidate.items + + if module.params['backup']: + backup_config(module, result) + + if configobjs: + commands = dumps(configobjs, 'commands').split('\n') + result['updates'] = commands + + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + # if the update mode is set to check just return + # and do not try to load into the system + if update != 'check': + load_config(module, commands, result) + + # remove the checkpoint file used to restore the config + # in case of an error + if not module.check_mode: + module.cli('no checkpoint %s' % result['__checkpoint__']) + + if module.params['save'] and not module.check_mode: + module.config.save_config() + result['changed'] = True + +def absent(module, result): + if not module.check_mode: + module.cli('write erase') + result['changed'] = True def main(): + """ main entry point for module execution + """ argument_spec = dict( - lines=dict(aliases=['commands'], required=True, type='list'), + lines=dict(aliases=['commands'], type='list'), parents=dict(type='list'), + + src=dict(type='path'), + before=dict(type='list'), after=dict(type='list'), - match=dict(default='line', choices=['line', 'strict', 'exact']), + + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), replace=dict(default='line', choices=['line', 'block']), - force=dict(default=False, type='bool'), - config=dict() - ) - module = get_module(argument_spec=argument_spec, - supports_check_mode=True) + # this argument is deprecated in favor of setting match: none + # it will be removed in a future version + force=dict(default=False, type='bool'), - lines = module.params['lines'] - parents = module.params['parents'] or list() + update=dict(choices=['merge', 'check'], default='merge'), + backup=dict(type='bool', default=False), - before = module.params['before'] - after = module.params['after'] + config=dict(), + defaults=dict(type='bool', default=False), - match = module.params['match'] - replace = module.params['replace'] + save=dict(type='bool', default=False), - contents = get_config(module) - config = module.parse_config(contents) + state=dict(default='present', choices=['absent', 'present']) + ) - if not module.params['force']: - contents = get_config(module) - config = NetworkConfig(contents=contents, indent=2) + mutually_exclusive = [('lines', 'src')] - candidate = NetworkConfig(indent=2) - candidate.add(lines, parents=parents) + module = NetworkModule(argument_spec=argument_spec, + connect_on_load=False, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) - commands = candidate.difference(config, path=parents, match=match, replace=replace) - else: - commands = parents - commands.extend(lines) + state = module.params['state'] - result = dict(changed=False) + if module.params['force'] is True: + module.params['match'] = 'none' - if commands: - if before: - commands[:0] = before + warnings = list() + check_args(module, warnings) - if after: - commands.extend(after) + result = dict(changed=False, warnings=warnings) - if not module.check_mode: - commands = [str(c).strip() for c in commands] - response = module.configure(commands) - result['responses'] = response - result['changed'] = True + try: + invoke(state, module, result) + except NetworkError: + load_checkpoint(module, result) + exc = get_exception() + module.fail_json(msg=str(exc), **exc.kwargs) - result['updates'] = commands module.exit_json(**result) -from ansible.module_utils.basic import * -from ansible.module_utils.urls import * -from ansible.module_utils.shell import * -from ansible.module_utils.netcfg import * -from ansible.module_utils.nxos import * if __name__ == '__main__': main()