diff --git a/lib/ansible/modules/network/nxos/nxos_config.py b/lib/ansible/modules/network/nxos/nxos_config.py index 4f6c0b66c2..b7e40dd1ed 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'] + 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'] - 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 -def get_config(module): - config = module.params['config'] or dict() - if not config and not module.params['force']: - config = module.config - return config +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 main(): +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') - argument_spec = dict( - lines=dict(aliases=['commands'], required=True, type='list'), - parents=dict(type='list'), - before=dict(type='list'), - after=dict(type='list'), - match=dict(default='line', choices=['line', 'strict', 'exact']), - replace=dict(default='line', choices=['line', 'block']), - force=dict(default=False, type='bool'), - config=dict() - ) +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 - module = get_module(argument_spec=argument_spec, - supports_check_mode=True) +def get_config(module, result): + defaults = module.params['defaults'] + if defaults is True: + key = '__configall__' + else: + key = '__config__' - lines = module.params['lines'] - parents = module.params['parents'] or list() + contents = module.params['config'] or result.get(key) - before = module.params['before'] - after = module.params['after'] + 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'] - contents = get_config(module) - config = module.parse_config(contents) + candidate = get_candidate(module) - if not module.params['force']: - contents = get_config(module) - config = NetworkConfig(contents=contents, indent=2) - - candidate = NetworkConfig(indent=2) - candidate.add(lines, parents=parents) - - commands = candidate.difference(config, path=parents, match=match, replace=replace) + if match != 'none': + config = get_config(module, result) + configobjs = candidate.difference(config, match=match, replace=replace) else: - commands = parents - commands.extend(lines) + config = None + configobjs = candidate.items - result = dict(changed=False) + if module.params['backup']: + backup_config(module, result) - if commands: - if before: - commands[:0] = before + if configobjs: + commands = dumps(configobjs, 'commands').split('\n') + result['updates'] = commands - if after: - commands.extend(after) + 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: - commands = [str(c).strip() for c in commands] - response = module.configure(commands) - result['responses'] = response + module.cli('no checkpoint %s' % result['__checkpoint__']) + + if module.params['save'] and not module.check_mode: + module.config.save_config() result['changed'] = True - result['updates'] = commands +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'], 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', 'none']), + replace=dict(default='line', choices=['line', 'block']), + + # this argument is deprecated in favor of setting match: none + # it will be removed in a future version + force=dict(default=False, type='bool'), + + update=dict(choices=['merge', 'check'], default='merge'), + backup=dict(type='bool', default=False), + + config=dict(), + defaults=dict(type='bool', default=False), + + save=dict(type='bool', default=False), + + state=dict(default='present', choices=['absent', 'present']) + ) + + mutually_exclusive = [('lines', 'src')] + + module = NetworkModule(argument_spec=argument_spec, + connect_on_load=False, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) + + state = module.params['state'] + + if module.params['force'] is True: + module.params['match'] = 'none' + + warnings = list() + check_args(module, warnings) + + result = dict(changed=False, warnings=warnings) + + try: + invoke(state, module, result) + except NetworkError: + load_checkpoint(module, result) + exc = get_exception() + module.fail_json(msg=str(exc), **exc.kwargs) + 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()