From 32bbddee238e28c2e24a4454230ee2b4e6a0da64 Mon Sep 17 00:00:00 2001 From: Peter Sprygada Date: Sun, 4 Sep 2016 08:10:44 -0400 Subject: [PATCH] update asa_config module * removes get_module() factory method for NetworkModule * 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 withoutdefaults * add save argument to save current running config to startup config --- .../modules/extras/network/asa/asa_config.py | 260 +++++++++++++----- 1 file changed, 191 insertions(+), 69 deletions(-) diff --git a/lib/ansible/modules/extras/network/asa/asa_config.py b/lib/ansible/modules/extras/network/asa/asa_config.py index 7c7d1248b41..36926227e45 100644 --- a/lib/ansible/modules/extras/network/asa/asa_config.py +++ b/lib/ansible/modules/extras/network/asa/asa_config.py @@ -20,7 +20,7 @@ DOCUMENTATION = """ --- module: asa_config version_added: "2.2" -author: "Peter Sprygada (@privateip) & Patrick Ogenstad (@ogenstad)" +author: "Peter Sprygada (@privateip), Patrick Ogenstad (@ogenstad)" short_description: Manage Cisco ASA configuration sections description: - Cisco ASA configurations use a simple block indent file sytanx @@ -34,9 +34,11 @@ options: - The ordered set of commands that should be configured in the section. The commands must be the exact same commands as found in the device running-config. Be sure to note the configuration - command syntanx as some commands are automatically modified by the + command syntax as some commands are automatically modified by the device config parser. - required: true + required: false + default: null + aliases: ['commands'] parents: description: - The ordered set of parents that uniquely identify the section @@ -45,6 +47,15 @@ options: level or global commands. required: false default: null + src: + description: + - Specifies the source path to the file that contains the configuration + or configuration template to load. The path to the source file can + either be the full path on the Ansible control host or a relative + path from the playbook or role root directory. This argument is mutually + exclusive with I(lines). + required: false + default: null before: description: - The ordered set of commands to push on to the command stack if @@ -57,7 +68,7 @@ options: after: description: - The ordered set of commands to append to the end of the command - stack if a changed needs to be made. Just like with I(before) this + stack if a change needs to be made. Just like with I(before) this allows the playbook designer to append a set of commands to be executed after the command set. required: false @@ -68,11 +79,13 @@ options: the set of commands against the current device config. If match is set to I(line), commands are matched line by line. If 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. + to position. If match is set to I(exact), command lines + must be an equal match. Finally, if match is set to I(none), the + module will not attempt to compare the source configuration with + the running configuration on the remote device. 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 @@ -84,35 +97,84 @@ options: required: false default: line choices: ['line', 'block'] - force: + 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'] + commit: 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. + - This argument specifies the update method to use when applying the + configuration changes to the remote node. If the value is set to + I(merge) the configuration updates are merged with the running- + config. If the value is set to I(check), no changes are made to + the remote host. required: false - default: false + default: merge + choices: ['merge', 'check'] + backup: + description: + - This argument will cause the module to create a full backup of + the current C(running-config) from the remote device before any + changes are made. The backup file is written to the C(backup) + folder in the playbook root directory. If the directory does not + exist, it is created. + required: false + default: no choices: ['yes', 'no'] 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. + - The C(config) argument allows the playbook desginer to supply + the base configuration to be used to validate configuration + changes necessary. If this argument is provided, the module + will not download the running-config from the remote node. required: false default: null + default: + description: + - This argument specifies whether or not to collect all defaults + when getting the remote device running config. When enabled, + the module will get the current config by issuing the command + C(show running-config all). + required: false + default: no + choices: ['yes', 'no'] + save: + description: + - The C(save) argument instructs the module to save the running- + config to the startup-config at the conclusion of the module + running. If check mode is specified, this argument is ignored. + required: false + default: no + choices: ['yes', 'no'] """ EXAMPLES = """ +# Note: examples below use the following provider dict to handle +# transport and authentication to the node. +vars: + cli: + host: "{{ inventory_hostname }}" + username: cisco + password: cisco + authorize: yes + auth_pass: cisco + transport: cli + - asa_config: lines: - network-object host 10.80.30.18 - network-object host 10.80.30.19 - network-object host 10.80.30.20 parents: ['object-group network OG-MONITORED-SERVERS'] + provider: "{{ cli }}" - asa_config: host: "{{ inventory_hostname }}" @@ -128,12 +190,11 @@ EXAMPLES = """ context: ansible - asa_config: - provider: "{{ cli }}" - host: "{{ inventory_hostname }}" show_command: 'more system:running-config' lines: - ikev1 pre-shared-key MyS3cretVPNK3y parents: tunnel-group 1.1.1.1 ipsec-attributes + provider: "{{ cli }}" """ @@ -143,79 +204,140 @@ updates: returned: always type: list sample: ['...', '...'] - +backup_path: + description: The full path to the backup file + returned: when backup is yes + type: path + sample: /playbooks/ansible/backup/config.2016-07-16@22:28:34 responses: description: The set of responses from issuing the commands on the device - retured: when not check_mode + returned: when not check_mode type: list sample: ['...', '...'] """ +import re + +from ansible.module_utils.basic import get_exception +from ansible.module_utils.asa import NetworkModule, NetworkError +from ansible.module_utils.netcfg import NetworkConfig, dumps +from ansible.module_utils.netcli import Command + +def invoke(name, *args, **kwargs): + func = globals().get(name) + if func: + return func(*args, **kwargs) -def get_config(module): - config = module.params['config'] or dict() - if not config and not module.params['force']: - config = module.config - return config +def check_args(module, warnings): + if module.params['parents']: + if not module.params['lines'] or module.params['src']: + warnings.append('ignoring unnecessary argument parents') + if module.params['match'] == 'none' and module.params['replace']: + warnings.append('ignoring unnecessary argument replace') +def get_config(module, result): + defaults = module.params['default'] + if defaults is True: + key = '__configall__' + else: + key = '__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 get_candidate(module): + candidate = NetworkConfig(indent=1) + 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 load_config(module, commands, result): + if not module.check_mode and module.params['update'] != 'check': + module.config(commands) + result['changed'] = module.params['update'] != 'check' + result['updates'] = commands.split('\n') + +def run(module, result): + match = module.params['match'] + replace = module.params['replace'] + + 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 configobjs: + commands = dumps(configobjs, 'commands') + + if module.params['before']: + commands[:0] = module.params['before'] + + if module.params['after']: + commands.extend(module.params['after']) + + # send the configuration commands to the device and merge + # them with the current running config + load_config(module, commands, result) + + if module.params['save'] and not module.check_mode: + module.config.save_config() def main(): 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']), - 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) - - lines = module.params['lines'] - parents = module.params['parents'] or list() + match=dict(default='line', choices=['line', 'strict', 'exact', 'none']), + replace=dict(default='line', choices=['line', 'block']), - before = module.params['before'] - after = module.params['after'] + update=dict(choices=['merge', 'check'], default='merge'), + backup=dict(type='bool', default=False), - match = module.params['match'] - replace = module.params['replace'] + config=dict(), + default=dict(type='bool', default=False), - if not module.params['force']: - contents = get_config(module) - config = NetworkConfig(contents=contents, indent=1) + save=dict(type='bool', default=False), + ) - candidate = NetworkConfig(indent=1) - candidate.add(lines, parents=parents) + mutually_exclusive = [('lines', 'src')] - commands = candidate.difference(config, path=parents, match=match, replace=replace) - else: - commands = parents - commands.extend(lines) + module = NetworkModule(argument_spec=argument_spec, + connect_on_load=False, + mutually_exclusive=mutually_exclusive, + supports_check_mode=True) - result = dict(changed=False) + warnings = list() + check_args(module, warnings) - if commands: - if before: - commands[:0] = before + result = dict(changed=False, warnings=warnings) - if after: - commands.extend(after) + if module.params['backup']: + result['__backup__'] = module.config.get_config() - if not module.check_mode: - commands = [str(c).strip() for c in commands] - response = module.configure(commands) - result['responses'] = response - result['changed'] = True + try: + run(module, result) + except NetworkError: + exc = get_exception() + module.fail_json(msg=str(exc)) - result['updates'] = commands module.exit_json(**result) -from ansible.module_utils.basic import * -from ansible.module_utils.shell import * -from ansible.module_utils.netcfg import * -from ansible.module_utils.asa import * if __name__ == '__main__': main()