update ops_config module with new enhancements

* 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 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

Note: this module only supports transport=cli

Tested on OpenSwitch 0.4.0
This commit is contained in:
Peter Sprygada 2016-08-30 17:52:31 -04:00
parent 38fd67bead
commit 2133b92980

View file

@ -20,7 +20,7 @@ DOCUMENTATION = """
--- ---
module: ops_config module: ops_config
version_added: "2.1" version_added: "2.1"
author: "Peter sprygada (@privateip)" author: "Peter Sprygada (@privateip)"
short_description: Manage OpenSwitch configuration using CLI short_description: Manage OpenSwitch configuration using CLI
description: description:
- OpenSwitch configurations use a simple block indent file syntax - OpenSwitch configurations use a simple block indent file syntax
@ -36,7 +36,8 @@ options:
in the device running-config. Be sure to note the configuration in the device running-config. Be sure to note the configuration
command syntax as some commands are automatically modified by the command syntax as some commands are automatically modified by the
device config parser. device config parser.
required: true required: false
default: null
parents: parents:
description: description:
- The ordered set of parents that uniquely identify the section - The ordered set of parents that uniquely identify the section
@ -45,6 +46,17 @@ options:
level or global commands. level or global commands.
required: false required: false
default: null 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: before:
description: description:
- The ordered set of commands to push on to the command stack if - The ordered set of commands to push on to the command stack if
@ -72,7 +84,7 @@ options:
must be an equal match. must be an equal match.
required: false required: false
default: line default: line
choices: ['line', 'strict', 'exact'] choices: ['line', 'strict', 'exact', 'none']
replace: replace:
description: description:
- Instructs the module on the way to perform the configuration - Instructs the module on the way to perform the configuration
@ -90,9 +102,25 @@ options:
current devices running-config. When set to true, this will current devices running-config. When set to true, this will
cause the module to push the contents of I(src) into the device cause the module to push the contents of I(src) into the device
without first checking if already configured. without first checking if already configured.
- Note this argument should be considered deprecated. To achieve
the equivalent, set the match argument to none. This argument
will be removed in a future release.
required: false required: false
default: false default: false
choices: ['true', 'false'] choices: ['yes', 'no']
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) I(replace) 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: config:
description: description:
- The module, by default, will connect to the remote device and - The module, by default, will connect to the remote device and
@ -104,20 +132,55 @@ options:
config for comparison. config for comparison.
required: false required: false
default: null default: null
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']
version_added: "2.2"
state:
description:
- This argument specifies whether or not the running-config is
present on the remote device. When set to I(absent) the
running-config on the remote device is erased.
required: false
default: no
choices: ['yes', 'no']
version_added: "2.2"
""" """
EXAMPLES = """ EXAMPLES = """
# Note: examples below use the following provider dict to handle
# transport and authentication to the node.
vars:
cli:
host: "{{ inventory_hostname }}"
username: netop
password: netop
- name: configure hostname over cli - name: configure hostname over cli
ops_config: ops_config:
lines: lines:
- "hostname {{ inventory_hostname }}" - "hostname {{ inventory_hostname }}"
provider: "{{ cli }}"
- name: configure vlan 10 over cli - name: configure vlan 10 over cli
ops_config: ops_config:
lines: lines:
- no shutdown - no shutdown
parents: parents:
- vlan 10 - vlan 10
provider: "{{ cli }}"
- name: load config from file
ops_config:
src: ops01.cfg
backup: yes
provider: "{{ cli }}"
""" """
RETURN = """ RETURN = """
@ -126,122 +189,166 @@ updates:
returned: always returned: always
type: list type: list
sample: ['...', '...'] sample: ['...', '...']
backup_path:
description: The full path to the backup file
returned: when backup is yes
type: path
sample: /playbooks/ansible/backup/ios_config.2016-07-16@22:28:34
responses: responses:
description: The set of responses from issuing the commands on the device description: The set of responses from issuing the commands on the device
retured: when not check_mode returned: when not check_mode
type: list type: list
sample: ['...', '...'] sample: ['...', '...']
""" """
import re import re
import itertools
def get_config(module): from ansible.module_utils.basic import get_exception
config = module.params['config'] or dict() from ansible.module_utils.openswitch import NetworkModule, NetworkError
if not config and not module.params['force']: from ansible.module_utils.netcfg import NetworkConfig, dumps
config = module.config from ansible.module_utils.netcli import Command
return config
def invoke(name, *args, **kwargs):
func = globals().get(name)
if func:
return func(*args, **kwargs)
def build_candidate(lines, parents, config, strategy): def check_args(module, warnings):
candidate = list() if module.params['parents']:
if not module.params['lines'] or module.params['src']:
warnings.append('ignoring unnecessary argument parents')
if module.params['force']:
warnings.append('The force argument is deprecated, please use '
'match=none instead. This argument will be '
'removed in the future')
if strategy == 'strict': def get_config(module, result):
for index, cmd in enumerate(lines): contents = module.params['config']
try: if not contents:
if cmd != config[index]: contents = module.config.get_config()
candidate.append(cmd) return NetworkConfig(indent=4, contents=contents)
except IndexError:
candidate.append(cmd)
elif strategy == 'exact':
if len(lines) != len(config):
candidate = list(lines)
else:
for cmd, cfg in itertools.izip(lines, config):
if cmd != cfg:
candidate = list(lines)
break
else:
for cmd in lines:
if cmd not in config:
candidate.append(cmd)
def get_candidate(module):
candidate = NetworkConfig(indent=4)
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 return candidate
def load_backup(module):
try:
module.cli(['exit', 'config replace flash:/ansible-rollback force'])
except NetworkError:
module.fail_json(msg='unable to rollback configuration')
def backup_config(module):
cmd = 'copy running-config flash:/ansible-rollback'
cmd = Command(cmd, prompt=re.compile('\? $'), response='\n')
module.cli(cmd)
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 present(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 absent(module, result):
if not module.check_mode:
module.cli('erase startup-config')
result['changed'] = True
def main(): def main():
argument_spec = dict( argument_spec = dict(
lines=dict(aliases=['commands'], required=True, type='list'), lines=dict(aliases=['commands'], type='list'),
parents=dict(type='list'), parents=dict(type='list'),
src=dict(type='path'),
before=dict(type='list'), before=dict(type='list'),
after=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']), 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'), force=dict(default=False, type='bool'),
update=dict(choices=['merge', 'check'], default='merge'),
backup=dict(type='bool', default=False),
config=dict(), config=dict(),
default=dict(type='bool', default=False),
save=dict(type='bool', default=False),
state=dict(choices=['present', 'absent'], default='present'),
# ops_config is only supported over Cli transport so force
# the value of transport to be cli
transport=dict(default='cli', choices=['cli']) transport=dict(default='cli', choices=['cli'])
) )
module = get_module(argument_spec=argument_spec, mutually_exclusive = [('lines', 'src')]
supports_check_mode=True)
lines = module.params['lines'] module = NetworkModule(argument_spec=argument_spec,
parents = module.params['parents'] or list() connect_on_load=False,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
before = module.params['before'] state = module.params['state']
after = module.params['after']
match = module.params['match'] if module.params['force'] is True:
replace = module.params['replace'] module.params['match'] = 'none'
contents = get_config(module) warnings = list()
config = module.parse_config(contents) check_args(module, warnings)
if parents: result = dict(changed=False, warnings=warnings)
for parent in parents:
for item in config:
if item.text == parent:
config = item
try: if module.params['backup']:
children = [c.text for c in config.children] result['__backup__'] = module.config.get_config()
except AttributeError:
children = [c.text for c in config]
else: try:
children = [c.text for c in config if not c.parents] invoke(state, module, result)
except NetworkError:
exc = get_exception()
module.fail_json(msg=str(exc))
result = dict(changed=False) module.exit_json(**result)
candidate = build_candidate(lines, parents, children, match)
if candidate:
if replace == 'line':
candidate[:0] = parents
else:
candidate = list(parents)
candidate.extend(lines)
if before:
candidate[:0] = before
if after:
candidate.extend(after)
if not module.check_mode:
response = module.configure(candidate)
result['responses'] = response
result['changed'] = True
result['updates'] = candidate
return 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.openswitch import *
if __name__ == '__main__': if __name__ == '__main__':
main() main()