Merge pull request #4610 from privateip/ops_config

update ops_config module with new enhancements
reviewable/pr18780/r1
Peter Sprygada 8 years ago committed by GitHub
commit 244c626822

@ -20,7 +20,7 @@ DOCUMENTATION = """
---
module: ops_config
version_added: "2.1"
author: "Peter sprygada (@privateip)"
author: "Peter Sprygada (@privateip)"
short_description: Manage OpenSwitch configuration using CLI
description:
- 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
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
@ -45,6 +46,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
@ -72,7 +84,7 @@ options:
must be an equal match.
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
@ -90,9 +102,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 equivalent, set the match argument to none. This argument
will be removed in a future release.
required: 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:
description:
- The module, by default, will connect to the remote device and
@ -104,13 +132,41 @@ options:
config for comparison.
required: false
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 = """
# 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
ops_config:
lines:
- "hostname {{ inventory_hostname }}"
provider: "{{ cli }}"
- name: configure vlan 10 over cli
ops_config:
@ -118,6 +174,13 @@ EXAMPLES = """
- no shutdown
parents:
- vlan 10
provider: "{{ cli }}"
- name: load config from file
ops_config:
src: ops01.cfg
backup: yes
provider: "{{ cli }}"
"""
RETURN = """
@ -126,122 +189,166 @@ 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/ios_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
import itertools
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.basic import get_exception
from ansible.module_utils.openswitch 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 build_candidate(lines, parents, config, strategy):
candidate = list()
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['force']:
warnings.append('The force argument is deprecated, please use '
'match=none instead. This argument will be '
'removed in the future')
if strategy == 'strict':
for index, cmd in enumerate(lines):
def get_config(module, result):
contents = module.params['config']
if not contents:
contents = module.config.get_config()
return NetworkConfig(indent=4, contents=contents)
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
def load_backup(module):
try:
if cmd != config[index]:
candidate.append(cmd)
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
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:
for cmd in lines:
if cmd not in config:
candidate.append(cmd)
config = None
configobjs = candidate.items
return candidate
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():
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']),
# this argument is deprecated in favor of setting match: none
# it will be removed in a future version
force=dict(default=False, type='bool'),
config=dict(),
transport=dict(default='cli', choices=['cli'])
)
module = get_module(argument_spec=argument_spec,
supports_check_mode=True)
update=dict(choices=['merge', 'check'], default='merge'),
backup=dict(type='bool', default=False),
lines = module.params['lines']
parents = module.params['parents'] or list()
config=dict(),
default=dict(type='bool', default=False),
before = module.params['before']
after = module.params['after']
save=dict(type='bool', default=False),
match = module.params['match']
replace = module.params['replace']
state=dict(choices=['present', 'absent'], default='present'),
contents = get_config(module)
config = module.parse_config(contents)
# ops_config is only supported over Cli transport so force
# the value of transport to be cli
transport=dict(default='cli', choices=['cli'])
)
if parents:
for parent in parents:
for item in config:
if item.text == parent:
config = item
mutually_exclusive = [('lines', 'src')]
try:
children = [c.text for c in config.children]
except AttributeError:
children = [c.text for c in config]
module = NetworkModule(argument_spec=argument_spec,
connect_on_load=False,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
else:
children = [c.text for c in config if not c.parents]
state = module.params['state']
result = dict(changed=False)
if module.params['force'] is True:
module.params['match'] = 'none'
candidate = build_candidate(lines, parents, children, match)
warnings = list()
check_args(module, warnings)
if candidate:
if replace == 'line':
candidate[:0] = parents
else:
candidate = list(parents)
candidate.extend(lines)
result = dict(changed=False, warnings=warnings)
if before:
candidate[:0] = before
if module.params['backup']:
result['__backup__'] = module.config.get_config()
if after:
candidate.extend(after)
try:
invoke(state, module, result)
except NetworkError:
exc = get_exception()
module.fail_json(msg=str(exc))
if not module.check_mode:
response = module.configure(candidate)
result['responses'] = response
result['changed'] = True
module.exit_json(**result)
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__':
main()

Loading…
Cancel
Save