mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
vyos and ios cliconf plugin refactor (#41846)
* vyos and ios cliconf plugin refactor * Refactor vyos cliconf plugin * Change vyos module_utils and vyos_config as per refactor * Minor changes in ios cliconf plugin * Fix unit test failure * Fix sanity issues * Add get_diff to rpc list
This commit is contained in:
parent
9c5d40ff15
commit
773c031d33
8 changed files with 285 additions and 101 deletions
|
@ -166,6 +166,7 @@ def load_config(module, commands):
|
|||
connection = get_connection(module)
|
||||
|
||||
try:
|
||||
return connection.edit_config(commands)
|
||||
diff, response = connection.edit_config(commands)
|
||||
return response
|
||||
except ConnectionError as exc:
|
||||
module.fail_json(msg=to_text(exc))
|
||||
|
|
|
@ -133,29 +133,8 @@ def load_config(module, commands, commit=False, comment=None):
|
|||
connection = get_connection(module)
|
||||
|
||||
try:
|
||||
out = connection.edit_config(commands)
|
||||
diff_config, resp = connection.edit_config(candidate=commands, commit=commit, diff=module._diff, comment=comment)
|
||||
except ConnectionError as exc:
|
||||
module.fail_json(msg=to_text(exc))
|
||||
|
||||
diff = None
|
||||
if module._diff:
|
||||
out = connection.get('compare')
|
||||
out = to_text(out, errors='surrogate_or_strict')
|
||||
|
||||
if not out.startswith('No changes'):
|
||||
out = connection.get('show')
|
||||
diff = to_text(out, errors='surrogate_or_strict').strip()
|
||||
|
||||
if commit:
|
||||
try:
|
||||
out = connection.commit(comment)
|
||||
except ConnectionError:
|
||||
connection.discard_changes()
|
||||
module.fail_json(msg='commit failed: %s' % out)
|
||||
else:
|
||||
connection.get('exit')
|
||||
else:
|
||||
connection.discard_changes()
|
||||
|
||||
if diff:
|
||||
return diff
|
||||
return diff_config
|
||||
|
|
|
@ -441,9 +441,9 @@ def main():
|
|||
# them with the current running config
|
||||
if not module.check_mode:
|
||||
if commands:
|
||||
connection.edit_config(commands)
|
||||
connection.edit_config(candidate=commands)
|
||||
if banner_diff:
|
||||
connection.edit_banner(json.dumps(banner_diff), multiline_delimiter=module.params['multiline_delimiter'])
|
||||
connection.edit_banner(candidate=json.dumps(banner_diff), multiline_delimiter=module.params['multiline_delimiter'])
|
||||
|
||||
result['changed'] = True
|
||||
|
||||
|
|
|
@ -130,11 +130,11 @@ backup_path:
|
|||
sample: /playbooks/ansible/backup/vyos_config.2016-07-16@22:28:34
|
||||
"""
|
||||
import re
|
||||
import json
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.common.config import NetworkConfig
|
||||
from ansible.module_utils.network.vyos.vyos import load_config, get_config, run_commands
|
||||
from ansible.module_utils.network.vyos.vyos import vyos_argument_spec
|
||||
from ansible.module_utils.network.vyos.vyos import vyos_argument_spec, get_connection
|
||||
|
||||
|
||||
DEFAULT_COMMENT = 'configured by vyos_config'
|
||||
|
@ -144,35 +144,13 @@ CONFIG_FILTERS = [
|
|||
]
|
||||
|
||||
|
||||
def config_to_commands(config):
|
||||
set_format = config.startswith('set') or config.startswith('delete')
|
||||
candidate = NetworkConfig(indent=4, contents=config)
|
||||
if not set_format:
|
||||
candidate = [c.line for c in candidate.items]
|
||||
commands = list()
|
||||
# this filters out less specific lines
|
||||
for item in candidate:
|
||||
for index, entry in enumerate(commands):
|
||||
if item.startswith(entry):
|
||||
del commands[index]
|
||||
break
|
||||
commands.append(item)
|
||||
|
||||
commands = ['set %s' % cmd.replace(' {', '') for cmd in commands]
|
||||
|
||||
else:
|
||||
commands = str(candidate).split('\n')
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
def get_candidate(module):
|
||||
contents = module.params['src'] or module.params['lines']
|
||||
|
||||
if module.params['lines']:
|
||||
contents = '\n'.join(contents)
|
||||
|
||||
return config_to_commands(contents)
|
||||
return contents
|
||||
|
||||
|
||||
def diff_config(commands, config):
|
||||
|
@ -225,7 +203,10 @@ def run(module, result):
|
|||
candidate = get_candidate(module)
|
||||
|
||||
# create loadable config that includes only the configuration updates
|
||||
commands = diff_config(candidate, config)
|
||||
connection = get_connection(module)
|
||||
response = connection.get_diff(candidate=candidate, running=config, match=module.params['match'])
|
||||
diff_obj = json.loads(response)
|
||||
commands = diff_obj.get('config_diff')
|
||||
sanitize_config(commands, result)
|
||||
|
||||
result['commands'] = commands
|
||||
|
@ -233,8 +214,9 @@ def run(module, result):
|
|||
commit = not module.check_mode
|
||||
comment = module.params['comment']
|
||||
|
||||
diff = None
|
||||
if commands:
|
||||
load_config(module, commands, commit=commit, comment=comment)
|
||||
diff = load_config(module, commands, commit=commit, comment=comment)
|
||||
|
||||
if result.get('filtered'):
|
||||
result['warnings'].append('Some configuration commands were '
|
||||
|
@ -242,6 +224,9 @@ def run(module, result):
|
|||
|
||||
result['changed'] = True
|
||||
|
||||
if module._diff:
|
||||
result['diff'] = diff
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
|
|
|
@ -93,7 +93,7 @@ class CliconfBase(with_metaclass(ABCMeta, object)):
|
|||
display.display('closing shell due to command timeout (%s seconds).' % self._connection._play_context.timeout, log_only=True)
|
||||
self.close()
|
||||
|
||||
def send_command(self, command, prompt=None, answer=None, sendonly=False, newline=True, prompt_retry_check=False):
|
||||
def send_command(self, command=None, prompt=None, answer=None, sendonly=False, newline=True, prompt_retry_check=False):
|
||||
"""Executes a command over the device connection
|
||||
|
||||
This method will execute a command over the device connection and
|
||||
|
@ -184,7 +184,7 @@ class CliconfBase(with_metaclass(ABCMeta, object)):
|
|||
pass
|
||||
|
||||
@abstractmethod
|
||||
def edit_config(self, candidate, check_mode=False, replace=None):
|
||||
def edit_config(self, candidate=None, commit=True, replace=False, diff=False, comment=None):
|
||||
"""Loads the candidate configuration into the network device
|
||||
|
||||
This method will load the specified candidate config into the device
|
||||
|
@ -195,20 +195,22 @@ class CliconfBase(with_metaclass(ABCMeta, object)):
|
|||
:param candidate: The configuration to load into the device and merge
|
||||
with the current running configuration
|
||||
|
||||
:param check_mode: Boolean value that indicates if the device candidate
|
||||
:param commit: Boolean value that indicates if the device candidate
|
||||
configuration should be pushed in the running configuration or discarded.
|
||||
|
||||
:param replace: Specifies the way in which provided config value should replace
|
||||
the configuration running on the remote device. If the device
|
||||
doesn't support config replace, an error is return.
|
||||
|
||||
:return: Returns response of executing the configuration command received
|
||||
from remote host
|
||||
:param replace: Boolean flag to indicate if running configuration should be completely
|
||||
replace by candidate configuration.
|
||||
:param diff: Boolean flag to indicate if configuration that is applied on remote host should
|
||||
generated and returned in response or not
|
||||
:param comment: Commit comment provided it is supported by remote host
|
||||
:return: Returns a tuple, the first entry of tupe is configuration diff if diff flag is enable else
|
||||
it is None. Second entry is the list of response received from remote host on executing
|
||||
configuration commands.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get(self, command, prompt=None, answer=None, sendonly=False, newline=True):
|
||||
def get(self, command=None, prompt=None, answer=None, sendonly=False, newline=True):
|
||||
"""Execute specified command on remote device
|
||||
This method will retrieve the specified data and
|
||||
return it to the caller as a string.
|
||||
|
@ -242,7 +244,7 @@ class CliconfBase(with_metaclass(ABCMeta, object)):
|
|||
'network_os_platform': <str>,
|
||||
},
|
||||
'device_operations': {
|
||||
'supports_replace': <bool>, # identify if config should be merged or replaced is supported
|
||||
'supports_diff_replace': <bool>, # identify if config should be merged or replaced is supported
|
||||
'supports_commit': <bool>, # identify if commit is supported by device or not
|
||||
'supports_rollback': <bool>, # identify if rollback is supported or not
|
||||
'supports_defaults': <bool>, # identify if fetching running config with default is supported
|
||||
|
@ -250,12 +252,13 @@ class CliconfBase(with_metaclass(ABCMeta, object)):
|
|||
'supports_onbox_diff: <bool>, # identify if on box diff capability is supported or not
|
||||
'supports_generate_diff: <bool>, # identify if diff capability is supported within plugin
|
||||
'supports_multiline_delimiter: <bool>, # identify if multiline demiliter is supported within config
|
||||
'support_match: <bool>, # identify if match is supported
|
||||
'support_diff_match: <bool>, # identify if match is supported
|
||||
'support_diff_ignore_lines: <bool>, # identify if ignore line in diff is supported
|
||||
'support_config_replace': <bool>, # identify if running config replace with candidate config is supported
|
||||
}
|
||||
'format': [list of supported configuration format],
|
||||
'match': ['line', 'strict', 'exact', 'none'],
|
||||
'replace': ['line', 'block', 'config'],
|
||||
'diff_match': [list of supported match values],
|
||||
'diff_replace': [list of supported replace values],
|
||||
}
|
||||
:return: capability as json string
|
||||
"""
|
||||
|
@ -326,3 +329,38 @@ class CliconfBase(with_metaclass(ABCMeta, object)):
|
|||
elif proto == 'sftp':
|
||||
with ssh.open_sftp() as sftp:
|
||||
sftp.get(source, destination)
|
||||
|
||||
def get_diff(self, candidate=None, running=None, match=None, diff_ignore_lines=None, path=None, replace=None):
|
||||
"""
|
||||
Generate diff between candidate and running configuration. If the
|
||||
remote host supports onbox diff capabilities ie. supports_onbox_diff in that case
|
||||
candidate and running configurations are not required to be passed as argument.
|
||||
In case if onbox diff capability is not supported candidate argument is mandatory
|
||||
and running argument is optional.
|
||||
:param candidate: The configuration which is expected to be present on remote host.
|
||||
:param running: The base configuration which is used to generate diff.
|
||||
:param match: Instructs how to match the candidate configuration with current device configuration
|
||||
Valid values are 'line', 'strict', 'exact', 'none'.
|
||||
'line' - commands are matched line by line
|
||||
'strict' - command lines are matched with respect to position
|
||||
'exact' - command lines must be an equal match
|
||||
'none' - will not compare the candidate configuration with the running configuration
|
||||
:param diff_ignore_lines: Use this argument to specify one or more lines that should be
|
||||
ignored during the diff. This is used for lines in the configuration
|
||||
that are automatically updated by the system. This argument takes
|
||||
a list of regular expressions or exact line matches.
|
||||
:param path: The ordered set of parents that uniquely identify the section or hierarchy
|
||||
the commands should be checked against. If the parents argument
|
||||
is omitted, the commands are checked against the set of top
|
||||
level or global commands.
|
||||
:param replace: Instructs on the way to perform the configuration on the device.
|
||||
If the replace argument is set to I(line) then the modified lines are
|
||||
pushed to the device in configuration mode. If the replace argument is
|
||||
set to I(block) then the entire command block is pushed to the device in
|
||||
configuration mode if any line is not correct.
|
||||
:return: Configuration and/or banner diff in json format.
|
||||
{
|
||||
'config_diff': ''
|
||||
}
|
||||
|
||||
"""
|
||||
|
|
|
@ -66,8 +66,7 @@ class Cliconf(CliconfBase):
|
|||
'line' - commands are matched line by line
|
||||
'strict' - command lines are matched with respect to position
|
||||
'exact' - command lines must be an equal match
|
||||
'none' - will not compare the candidate configuration with
|
||||
the running configuration on the remote device
|
||||
'none' - will not compare the candidate configuration with the running configuration
|
||||
:param diff_ignore_lines: Use this argument to specify one or more lines that should be
|
||||
ignored during the diff. This is used for lines in the configuration
|
||||
that are automatically updated by the system. This argument takes
|
||||
|
@ -90,9 +89,16 @@ class Cliconf(CliconfBase):
|
|||
"""
|
||||
diff = {}
|
||||
device_operations = self.get_device_operations()
|
||||
option_values = self.get_option_values()
|
||||
|
||||
if candidate is None and not device_operations['supports_onbox_diff']:
|
||||
raise ValueError('candidate configuration is required to generate diff')
|
||||
raise ValueError("candidate configuration is required to generate diff")
|
||||
|
||||
if match not in option_values['diff_match']:
|
||||
raise ValueError("'match' value %s in invalid, valid values are %s" % (match, option_values['diff_match']))
|
||||
|
||||
if replace not in option_values['diff_replace']:
|
||||
raise ValueError("'replace' value %s in invalid, valid values are %s" % (replace, option_values['diff_replace']))
|
||||
|
||||
# prepare candidate configuration
|
||||
candidate_obj = NetworkConfig(indent=1)
|
||||
|
@ -118,19 +124,22 @@ class Cliconf(CliconfBase):
|
|||
return json.dumps(diff)
|
||||
|
||||
@enable_mode
|
||||
def edit_config(self, candidate, check_mode=False, replace=None):
|
||||
def edit_config(self, candidate=None, commit=True, replace=False, diff=False, comment=None):
|
||||
if not candidate:
|
||||
raise ValueError('must provide a candidate config to load')
|
||||
raise ValueError("must provide a candidate config to load")
|
||||
|
||||
if check_mode not in (True, False):
|
||||
raise ValueError('`check_mode` must be a bool, got %s' % check_mode)
|
||||
if commit not in (True, False):
|
||||
raise ValueError("'commit' must be a bool, got %s" % commit)
|
||||
|
||||
options = self.get_option_values()
|
||||
if replace and replace not in options['replace']:
|
||||
raise ValueError('`replace` value %s in invalid, valid values are %s' % (replace, options['replace']))
|
||||
if replace not in (True, False):
|
||||
raise ValueError("'replace' must be a bool, got %s" % replace)
|
||||
|
||||
operations = self.get_device_operations()
|
||||
if replace and not operations['supports_replace']:
|
||||
raise ValueError("configuration replace is not supported on ios")
|
||||
|
||||
results = []
|
||||
if not check_mode:
|
||||
if commit:
|
||||
for line in chain(['configure terminal'], to_list(candidate)):
|
||||
if not isinstance(line, collections.Mapping):
|
||||
line = {'command': line}
|
||||
|
@ -140,16 +149,23 @@ class Cliconf(CliconfBase):
|
|||
results.append(self.send_command(**line))
|
||||
|
||||
results.append(self.send_command('end'))
|
||||
return results[1:-1]
|
||||
|
||||
def get(self, command, prompt=None, answer=None, sendonly=False):
|
||||
return self.send_command(command, prompt=prompt, answer=answer, sendonly=sendonly)
|
||||
diff_config = None
|
||||
if diff:
|
||||
diff_config = candidate
|
||||
|
||||
return diff_config, results[1:-1]
|
||||
|
||||
def get(self, command=None, prompt=None, answer=None, sendonly=False):
|
||||
if not command:
|
||||
raise ValueError('must provide value of command to execute')
|
||||
return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly)
|
||||
|
||||
def get_device_info(self):
|
||||
device_info = {}
|
||||
|
||||
device_info['network_os'] = 'ios'
|
||||
reply = self.get('show version')
|
||||
reply = self.get(command='show version')
|
||||
data = to_text(reply, errors='surrogate_or_strict').strip()
|
||||
|
||||
match = re.search(r'Version (\S+)', data)
|
||||
|
@ -168,47 +184,50 @@ class Cliconf(CliconfBase):
|
|||
|
||||
def get_device_operations(self):
|
||||
return {
|
||||
'supports_replace': True,
|
||||
'supports_diff_replace': True,
|
||||
'supports_commit': False,
|
||||
'supports_rollback': False,
|
||||
'supports_defaults': True,
|
||||
'supports_onbox_diff': False,
|
||||
'supports_commit_comment': False,
|
||||
'supports_multiline_delimiter': False,
|
||||
'support_match': True,
|
||||
'support_diff_match': True,
|
||||
'support_diff_ignore_lines': True,
|
||||
'supports_generate_diff': True,
|
||||
'supports_replace': False
|
||||
}
|
||||
|
||||
def get_option_values(self):
|
||||
return {
|
||||
'format': ['text'],
|
||||
'match': ['line', 'strict', 'exact', 'none'],
|
||||
'replace': ['line', 'block']
|
||||
'diff_match': ['line', 'strict', 'exact', 'none'],
|
||||
'diff_replace': ['line', 'block']
|
||||
}
|
||||
|
||||
def get_capabilities(self):
|
||||
result = dict()
|
||||
result['rpc'] = self.get_base_rpc() + ['edit_banner']
|
||||
result['rpc'] = self.get_base_rpc() + ['edit_banner', 'get_diff']
|
||||
result['network_api'] = 'cliconf'
|
||||
result['device_info'] = self.get_device_info()
|
||||
result['device_operations'] = self.get_device_operations()
|
||||
result.update(self.get_option_values())
|
||||
return json.dumps(result)
|
||||
|
||||
def edit_banner(self, banners, multiline_delimiter="@", check_mode=False):
|
||||
def edit_banner(self, candidate=None, multiline_delimiter="@", commit=True, diff=False):
|
||||
"""
|
||||
Edit banner on remote device
|
||||
:param banners: Banners to be loaded in json format
|
||||
:param multiline_delimiter: Line delimiter for banner
|
||||
:param check_mode: Boolean value that indicates if the device candidate
|
||||
:param commit: Boolean value that indicates if the device candidate
|
||||
configuration should be pushed in the running configuration or discarded.
|
||||
:param diff: Boolean flag to indicate if configuration that is applied on remote host should
|
||||
generated and returned in response or not
|
||||
:return: Returns response of executing the configuration command received
|
||||
from remote host
|
||||
"""
|
||||
banners_obj = json.loads(banners)
|
||||
banners_obj = json.loads(candidate)
|
||||
results = []
|
||||
if not check_mode:
|
||||
if commit:
|
||||
for key, value in iteritems(banners_obj):
|
||||
key += ' %s' % multiline_delimiter
|
||||
for cmd in ['config terminal', key, value, multiline_delimiter, 'end']:
|
||||
|
@ -218,7 +237,11 @@ class Cliconf(CliconfBase):
|
|||
time.sleep(0.1)
|
||||
results.append(self.send_command('\n'))
|
||||
|
||||
return results[1:-1]
|
||||
diff_banner = None
|
||||
if diff:
|
||||
diff_banner = candidate
|
||||
|
||||
return diff_banner, results[1:-1]
|
||||
|
||||
def _extract_banners(self, config):
|
||||
banners = {}
|
||||
|
|
|
@ -24,7 +24,9 @@ import json
|
|||
|
||||
from itertools import chain
|
||||
|
||||
from ansible.errors import AnsibleConnectionFailure
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.network.common.config import NetworkConfig, dumps
|
||||
from ansible.module_utils.network.common.utils import to_list
|
||||
from ansible.plugins.cliconf import CliconfBase
|
||||
|
||||
|
@ -51,14 +53,56 @@ class Cliconf(CliconfBase):
|
|||
|
||||
return device_info
|
||||
|
||||
def get_config(self, source='running', format='text'):
|
||||
return self.send_command('show configuration commands')
|
||||
def get_config(self, filter=None, format='set'):
|
||||
if format == 'text':
|
||||
out = self.send_command('show configuration')
|
||||
else:
|
||||
out = self.send_command('show configuration commands')
|
||||
return out
|
||||
|
||||
def edit_config(self, command):
|
||||
for cmd in chain(['configure'], to_list(command)):
|
||||
self.send_command(cmd)
|
||||
def edit_config(self, candidate=None, commit=True, replace=False, diff=False, comment=None):
|
||||
if not candidate:
|
||||
raise ValueError('must provide a candidate config to load')
|
||||
|
||||
def get(self, command, prompt=None, answer=None, sendonly=False):
|
||||
if commit not in (True, False):
|
||||
raise ValueError("'commit' must be a bool, got %s" % commit)
|
||||
|
||||
if replace not in (True, False):
|
||||
raise ValueError("'replace' must be a bool, got %s" % replace)
|
||||
|
||||
operations = self.get_device_operations()
|
||||
if replace and not operations['supports_replace']:
|
||||
raise ValueError("configuration replace is not supported on vyos")
|
||||
|
||||
results = []
|
||||
|
||||
for cmd in chain(['configure'], to_list(candidate)):
|
||||
results.append(self.send_command(cmd))
|
||||
|
||||
diff_config = None
|
||||
if diff:
|
||||
out = self.get('compare')
|
||||
out = to_text(out, errors='surrogate_or_strict')
|
||||
if not out.startswith('No changes'):
|
||||
diff_config = out
|
||||
|
||||
if commit:
|
||||
try:
|
||||
self.commit(comment)
|
||||
except AnsibleConnectionFailure as e:
|
||||
msg = 'commit failed: %s' % e.message
|
||||
self.discard_changes()
|
||||
raise AnsibleConnectionFailure(msg)
|
||||
else:
|
||||
self.get('exit')
|
||||
else:
|
||||
self.discard_changes()
|
||||
|
||||
return diff_config, results[1:]
|
||||
|
||||
def get(self, command=None, prompt=None, answer=None, sendonly=False):
|
||||
if not command:
|
||||
raise ValueError('must provide value of command to execute')
|
||||
return self.send_command(command, prompt=prompt, answer=answer, sendonly=sendonly)
|
||||
|
||||
def commit(self, comment=None):
|
||||
|
@ -68,12 +112,105 @@ class Cliconf(CliconfBase):
|
|||
command = 'commit'
|
||||
self.send_command(command)
|
||||
|
||||
def discard_changes(self, *args, **kwargs):
|
||||
def discard_changes(self):
|
||||
self.send_command('exit discard')
|
||||
|
||||
def get_diff(self, candidate=None, running=None, match='line', diff_ignore_lines=None, path=None, replace=None):
|
||||
diff = {}
|
||||
device_operations = self.get_device_operations()
|
||||
option_values = self.get_option_values()
|
||||
|
||||
if candidate is None and not device_operations['supports_onbox_diff']:
|
||||
raise ValueError("candidate configuration is required to generate diff")
|
||||
|
||||
if match not in option_values['diff_match']:
|
||||
raise ValueError("'match' value %s in invalid, valid values are %s" % (match, option_values['diff_match']))
|
||||
|
||||
if replace:
|
||||
raise ValueError("'replace' in diff is not supported on vyos")
|
||||
|
||||
if diff_ignore_lines:
|
||||
raise ValueError("'diff_ignore_lines' in diff is not supported on vyos")
|
||||
|
||||
if path:
|
||||
raise ValueError("'path' in diff is not supported on vyos")
|
||||
|
||||
set_format = candidate.startswith('set') or candidate.startswith('delete')
|
||||
candidate_obj = NetworkConfig(indent=4, contents=candidate)
|
||||
if not set_format:
|
||||
config = [c.line for c in candidate_obj.items]
|
||||
commands = list()
|
||||
# this filters out less specific lines
|
||||
for item in config:
|
||||
for index, entry in enumerate(commands):
|
||||
if item.startswith(entry):
|
||||
del commands[index]
|
||||
break
|
||||
commands.append(item)
|
||||
|
||||
candidate_commands = ['set %s' % cmd.replace(' {', '') for cmd in commands]
|
||||
|
||||
else:
|
||||
candidate_commands = str(candidate).strip().split('\n')
|
||||
|
||||
if match == 'none':
|
||||
diff['config_diff'] = list(candidate_commands)
|
||||
return json.dumps(diff)
|
||||
|
||||
running_commands = [str(c).replace("'", '') for c in running.splitlines()]
|
||||
|
||||
updates = list()
|
||||
visited = set()
|
||||
|
||||
for line in candidate_commands:
|
||||
item = str(line).replace("'", '')
|
||||
|
||||
if not item.startswith('set') and not item.startswith('delete'):
|
||||
raise ValueError('line must start with either `set` or `delete`')
|
||||
|
||||
elif item.startswith('set') and item not in running_commands:
|
||||
updates.append(line)
|
||||
|
||||
elif item.startswith('delete'):
|
||||
if not running_commands:
|
||||
updates.append(line)
|
||||
else:
|
||||
item = re.sub(r'delete', 'set', item)
|
||||
for entry in running_commands:
|
||||
if entry.startswith(item) and line not in visited:
|
||||
updates.append(line)
|
||||
visited.add(line)
|
||||
|
||||
diff['config_diff'] = list(updates)
|
||||
return json.dumps(diff)
|
||||
|
||||
def get_device_operations(self):
|
||||
return {
|
||||
'supports_diff_replace': False,
|
||||
'supports_commit': True,
|
||||
'supports_rollback': True,
|
||||
'supports_defaults': False,
|
||||
'supports_onbox_diff': False,
|
||||
'supports_commit_comment': True,
|
||||
'supports_multiline_delimiter': False,
|
||||
'support_diff_match': True,
|
||||
'support_diff_ignore_lines': False,
|
||||
'supports_generate_diff': True,
|
||||
'supports_replace': False
|
||||
}
|
||||
|
||||
def get_option_values(self):
|
||||
return {
|
||||
'format': ['set', 'text'],
|
||||
'diff_match': ['line', 'none'],
|
||||
'diff_replace': [],
|
||||
}
|
||||
|
||||
def get_capabilities(self):
|
||||
result = {}
|
||||
result['rpc'] = self.get_base_rpc() + ['commit', 'discard_changes']
|
||||
result['rpc'] = self.get_base_rpc() + ['commit', 'discard_changes', 'get_diff']
|
||||
result['network_api'] = 'cliconf'
|
||||
result['device_info'] = self.get_device_info()
|
||||
result['device_operations'] = self.get_device_operations()
|
||||
result.update(self.get_option_values())
|
||||
return json.dumps(result)
|
||||
|
|
|
@ -20,8 +20,9 @@
|
|||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.compat.tests.mock import patch
|
||||
from ansible.compat.tests.mock import patch, MagicMock
|
||||
from ansible.modules.network.vyos import vyos_config
|
||||
from ansible.plugins.cliconf.vyos import Cliconf
|
||||
from units.modules.utils import set_module_args
|
||||
from .vyos_module import TestVyosModule, load_fixture
|
||||
|
||||
|
@ -42,12 +43,23 @@ class TestVyosConfigModule(TestVyosModule):
|
|||
self.mock_run_commands = patch('ansible.modules.network.vyos.vyos_config.run_commands')
|
||||
self.run_commands = self.mock_run_commands.start()
|
||||
|
||||
self.mock_get_connection = patch('ansible.modules.network.vyos.vyos_config.get_connection')
|
||||
self.get_connection = self.mock_get_connection.start()
|
||||
|
||||
self.cliconf_obj = Cliconf(MagicMock())
|
||||
self.running_config = load_fixture('vyos_config_config.cfg')
|
||||
|
||||
self.conn = self.get_connection()
|
||||
self.conn.edit_config = MagicMock()
|
||||
self.running_config = load_fixture('vyos_config_config.cfg')
|
||||
|
||||
def tearDown(self):
|
||||
super(TestVyosConfigModule, self).tearDown()
|
||||
|
||||
self.mock_get_config.stop()
|
||||
self.mock_load_config.stop()
|
||||
self.mock_run_commands.stop()
|
||||
self.mock_get_connection.stop()
|
||||
|
||||
def load_fixtures(self, commands=None):
|
||||
config_file = 'vyos_config_config.cfg'
|
||||
|
@ -56,6 +68,7 @@ class TestVyosConfigModule(TestVyosModule):
|
|||
|
||||
def test_vyos_config_unchanged(self):
|
||||
src = load_fixture('vyos_config_config.cfg')
|
||||
self.conn.get_diff = MagicMock(return_value=self.cliconf_obj.get_diff(src, src))
|
||||
set_module_args(dict(src=src))
|
||||
self.execute_module()
|
||||
|
||||
|
@ -63,12 +76,14 @@ class TestVyosConfigModule(TestVyosModule):
|
|||
src = load_fixture('vyos_config_src.cfg')
|
||||
set_module_args(dict(src=src))
|
||||
commands = ['set system host-name foo', 'delete interfaces ethernet eth0 address']
|
||||
self.conn.get_diff = MagicMock(return_value=self.cliconf_obj.get_diff(src, self.running_config))
|
||||
self.execute_module(changed=True, commands=commands)
|
||||
|
||||
def test_vyos_config_src_brackets(self):
|
||||
src = load_fixture('vyos_config_src_brackets.cfg')
|
||||
set_module_args(dict(src=src))
|
||||
commands = ['set interfaces ethernet eth0 address 10.10.10.10/24', 'set system host-name foo']
|
||||
self.conn.get_diff = MagicMock(return_value=self.cliconf_obj.get_diff(src, self.running_config))
|
||||
self.execute_module(changed=True, commands=commands)
|
||||
|
||||
def test_vyos_config_backup(self):
|
||||
|
@ -79,16 +94,22 @@ class TestVyosConfigModule(TestVyosModule):
|
|||
def test_vyos_config_lines(self):
|
||||
commands = ['set system host-name foo']
|
||||
set_module_args(dict(lines=commands))
|
||||
candidate = '\n'.join(commands)
|
||||
self.conn.get_diff = MagicMock(return_value=self.cliconf_obj.get_diff(candidate, self.running_config))
|
||||
self.execute_module(changed=True, commands=commands)
|
||||
|
||||
def test_vyos_config_config(self):
|
||||
config = 'set system host-name localhost'
|
||||
new_config = ['set system host-name router']
|
||||
set_module_args(dict(lines=new_config, config=config))
|
||||
candidate = '\n'.join(new_config)
|
||||
self.conn.get_diff = MagicMock(return_value=self.cliconf_obj.get_diff(candidate, config))
|
||||
self.execute_module(changed=True, commands=new_config)
|
||||
|
||||
def test_vyos_config_match_none(self):
|
||||
lines = ['set system interfaces ethernet eth0 address 1.2.3.4/24',
|
||||
'set system interfaces ethernet eth0 description test string']
|
||||
set_module_args(dict(lines=lines, match='none'))
|
||||
candidate = '\n'.join(lines)
|
||||
self.conn.get_diff = MagicMock(return_value=self.cliconf_obj.get_diff(candidate, None, match='none'))
|
||||
self.execute_module(changed=True, commands=lines, sort=False)
|
||||
|
|
Loading…
Reference in a new issue