mirror of
				https://github.com/ansible-collections/community.general.git
				synced 2025-10-25 05:23:58 -07:00 
			
		
		
		
	nxos cliconf plugin refactor (#43203)
* nxos cliconf plugin refactor Fixes #39056 * Refactor nxos cliconf plugin as per new api definition * Minor changes in ios, eos, vyos cliconf plugin * Change nxos httpapi plugin edit_config method to be in sync with nxos cliconf edit_config * Fix CI failure * Fix unit test failure and review comment
This commit is contained in:
		
					parent
					
						
							
								e215f842ba
							
						
					
				
			
			
				commit
				
					
						af3f510316
					
				
			
		
					 16 changed files with 426 additions and 245 deletions
				
			
		|  | @ -19,6 +19,7 @@ | |||
| from __future__ import (absolute_import, division, print_function) | ||||
| __metaclass__ = type | ||||
| 
 | ||||
| import collections | ||||
| import json | ||||
| import re | ||||
| 
 | ||||
|  | @ -27,30 +28,30 @@ from itertools import chain | |||
| from ansible.errors import AnsibleConnectionFailure | ||||
| from ansible.module_utils._text import to_bytes, to_text | ||||
| from ansible.module_utils.connection import ConnectionError | ||||
| 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 | ||||
| from ansible.plugins.cliconf import CliconfBase, enable_mode | ||||
| from ansible.plugins.connection.network_cli import Connection as NetworkCli | ||||
| from ansible.plugins.connection.httpapi import Connection as HttpApi | ||||
| 
 | ||||
| 
 | ||||
| class Cliconf(CliconfBase): | ||||
| 
 | ||||
|     def send_command(self, command, prompt=None, answer=None, sendonly=False, newline=True, prompt_retry_check=False): | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super(Cliconf, self).__init__(*args, **kwargs) | ||||
| 
 | ||||
|     def send_command(self, command, **kwargs): | ||||
|         """Executes a cli command and returns the results | ||||
|         This method will execute the CLI command on the connection and return | ||||
|         the results to the caller.  The command output will be returned as a | ||||
|         string | ||||
|         """ | ||||
|         kwargs = {'command': to_bytes(command), 'sendonly': sendonly, | ||||
|                   'newline': newline, 'prompt_retry_check': prompt_retry_check} | ||||
|         if prompt is not None: | ||||
|             kwargs['prompt'] = to_bytes(prompt) | ||||
|         if answer is not None: | ||||
|             kwargs['answer'] = to_bytes(answer) | ||||
| 
 | ||||
|         if isinstance(self._connection, NetworkCli): | ||||
|             resp = self._connection.send(**kwargs) | ||||
|         else: | ||||
|             resp = super(Cliconf, self).send_command(command, **kwargs) | ||||
|         elif isinstance(self._connection, HttpApi): | ||||
|             resp = self._connection.send_request(command, **kwargs) | ||||
|         else: | ||||
|             raise ValueError("Invalid connection type") | ||||
|         return resp | ||||
| 
 | ||||
|     def get_device_info(self): | ||||
|  | @ -101,66 +102,169 @@ class Cliconf(CliconfBase): | |||
| 
 | ||||
|         return device_info | ||||
| 
 | ||||
|     def get_config(self, source='running', format='text', flags=None): | ||||
|     def get_diff(self, candidate=None, running=None, diff_match='line', diff_ignore_lines=None, path=None, diff_replace='line'): | ||||
|         diff = {} | ||||
|         device_operations = self.get_device_operations() | ||||
|         option_values = self.get_option_values() | ||||
| 
 | ||||
|         if candidate is None and device_operations['supports_generate_diff']: | ||||
|             raise ValueError("candidate configuration is required to generate diff") | ||||
| 
 | ||||
|         if diff_match not in option_values['diff_match']: | ||||
|             raise ValueError("'match' value %s in invalid, valid values are %s" % (diff_match, ', '.join(option_values['diff_match']))) | ||||
| 
 | ||||
|         if diff_replace not in option_values['diff_replace']: | ||||
|             raise ValueError("'replace' value %s in invalid, valid values are %s" % (diff_replace, ', '.join(option_values['diff_replace']))) | ||||
| 
 | ||||
|         # prepare candidate configuration | ||||
|         candidate_obj = NetworkConfig(indent=2) | ||||
|         candidate_obj.load(candidate) | ||||
| 
 | ||||
|         if running and diff_match != 'none' and diff_replace != 'config': | ||||
|             # running configuration | ||||
|             running_obj = NetworkConfig(indent=2, contents=running, ignore_lines=diff_ignore_lines) | ||||
|             configdiffobjs = candidate_obj.difference(running_obj, path=path, match=diff_match, replace=diff_replace) | ||||
| 
 | ||||
|         else: | ||||
|             configdiffobjs = candidate_obj.items | ||||
| 
 | ||||
|         diff['config_diff'] = dumps(configdiffobjs, 'commands') if configdiffobjs else '' | ||||
|         return diff | ||||
| 
 | ||||
|     def get_config(self, source='running', format='text', filter=None): | ||||
|         options_values = self.get_option_values() | ||||
|         if format not in options_values['format']: | ||||
|             raise ValueError("'format' value %s is invalid. Valid values are %s" % (format, ','.join(options_values['format']))) | ||||
| 
 | ||||
|         lookup = {'running': 'running-config', 'startup': 'startup-config'} | ||||
|         if source not in lookup: | ||||
|             return self.invalid_params("fetching configuration from %s is not supported" % source) | ||||
| 
 | ||||
|         cmd = 'show {0} '.format(lookup[source]) | ||||
|         if flags: | ||||
|             cmd += ' '.join(flags) | ||||
|         if format and format is not 'text': | ||||
|             cmd += '| %s ' % format | ||||
| 
 | ||||
|         if filter: | ||||
|             cmd += ' '.join(to_list(filter)) | ||||
|         cmd = cmd.strip() | ||||
| 
 | ||||
|         return self.send_command(cmd) | ||||
| 
 | ||||
|     def edit_config(self, command): | ||||
|         responses = [] | ||||
|         for cmd in chain(['configure'], to_list(command), ['end']): | ||||
|             responses.append(self.send_command(cmd)) | ||||
|         resp = responses[1:-1] | ||||
|         return json.dumps(resp) | ||||
|     def edit_config(self, candidate=None, commit=True, replace=None, comment=None): | ||||
|         resp = {} | ||||
|         operations = self.get_device_operations() | ||||
|         self.check_edit_config_capabiltiy(operations, candidate, commit, replace, comment) | ||||
|         results = [] | ||||
|         requests = [] | ||||
| 
 | ||||
|     def get(self, command, prompt=None, answer=None, sendonly=False): | ||||
|         if replace: | ||||
|             candidate = 'config replace {0}'.format(replace) | ||||
| 
 | ||||
|         if commit: | ||||
|             self.send_command('configure terminal') | ||||
| 
 | ||||
|             for line in to_list(candidate): | ||||
|                 if not isinstance(line, collections.Mapping): | ||||
|                     line = {'command': line} | ||||
| 
 | ||||
|                 cmd = line['command'] | ||||
|                 if cmd != 'end': | ||||
|                     results.append(self.send_command(**line)) | ||||
|                     requests.append(cmd) | ||||
| 
 | ||||
|             self.send_command('end') | ||||
|         else: | ||||
|             raise ValueError('check mode is not supported') | ||||
| 
 | ||||
|         resp['request'] = requests | ||||
|         resp['response'] = results | ||||
|         return resp | ||||
| 
 | ||||
|     def get(self, command, prompt=None, answer=None, sendonly=False, output=None): | ||||
|         if output: | ||||
|             command = self._get_command_with_output(command, output) | ||||
|         return self.send_command(command, prompt=prompt, answer=answer, sendonly=sendonly) | ||||
| 
 | ||||
|     def get_capabilities(self): | ||||
|         result = {} | ||||
|         result['rpc'] = self.get_base_rpc() | ||||
|         result['device_info'] = self.get_device_info() | ||||
|         if isinstance(self._connection, NetworkCli): | ||||
|             result['network_api'] = 'cliconf' | ||||
|         else: | ||||
|             result['network_api'] = 'nxapi' | ||||
|         return json.dumps(result) | ||||
|     def run_commands(self, commands=None, check_rc=True): | ||||
|         if commands is None: | ||||
|             raise ValueError("'commands' value is required") | ||||
| 
 | ||||
|     # Migrated from module_utils | ||||
|     def run_commands(self, commands, check_rc=True): | ||||
|         """Run list of commands on remote device and return results | ||||
|         """ | ||||
|         responses = list() | ||||
|         for cmd in to_list(commands): | ||||
|             if not isinstance(cmd, collections.Mapping): | ||||
|                 cmd = {'command': cmd} | ||||
| 
 | ||||
|         for item in to_list(commands): | ||||
|             if item['output'] == 'json' and not item['command'].endswith('| json'): | ||||
|                 cmd = '%s | json' % item['command'] | ||||
|             elif item['output'] == 'text' and item['command'].endswith('| json'): | ||||
|                 cmd = item['command'].rsplit('|', 1)[0] | ||||
|             else: | ||||
|                 cmd = item['command'] | ||||
|             output = cmd.pop('output', None) | ||||
|             if output: | ||||
|                 cmd['command'] = self._get_command_with_output(cmd['command'], output) | ||||
| 
 | ||||
|             try: | ||||
|                 out = self.get(cmd) | ||||
|                 out = self.send_command(**cmd) | ||||
|             except AnsibleConnectionFailure as e: | ||||
|                 if check_rc: | ||||
|                     raise | ||||
|                 out = getattr(e, 'err', e) | ||||
| 
 | ||||
|             try: | ||||
|                 out = to_text(out, errors='surrogate_or_strict').strip() | ||||
|             except UnicodeError: | ||||
|                 raise ConnectionError(msg=u'Failed to decode output from %s: %s' % (cmd, to_text(out))) | ||||
|             if out is not None: | ||||
|                 try: | ||||
|                     out = to_text(out, errors='surrogate_or_strict').strip() | ||||
|                 except UnicodeError: | ||||
|                     raise ConnectionError(msg=u'Failed to decode output from %s: %s' % (cmd, to_text(out))) | ||||
| 
 | ||||
|             try: | ||||
|                 out = json.loads(out) | ||||
|             except ValueError: | ||||
|                 pass | ||||
|                 try: | ||||
|                     out = json.loads(out) | ||||
|                 except ValueError: | ||||
|                     out = to_text(out, errors='surrogate_or_strict').strip() | ||||
| 
 | ||||
|             responses.append(out) | ||||
|                 responses.append(out) | ||||
|         return responses | ||||
| 
 | ||||
|     def get_device_operations(self): | ||||
|         return { | ||||
|             '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, | ||||
|             'supports_diff_match': True, | ||||
|             'supports_diff_ignore_lines': True, | ||||
|             'supports_generate_diff': True, | ||||
|             'supports_replace': True | ||||
|         } | ||||
| 
 | ||||
|     def get_option_values(self): | ||||
|         return { | ||||
|             'format': ['text', 'json'], | ||||
|             'diff_match': ['line', 'strict', 'exact', 'none'], | ||||
|             'diff_replace': ['line', 'block', 'config'], | ||||
|             'output': ['text', 'json'] | ||||
|         } | ||||
| 
 | ||||
|     def get_capabilities(self): | ||||
|         result = {} | ||||
|         result['rpc'] = self.get_base_rpc() | ||||
|         result['device_info'] = self.get_device_info() | ||||
|         result.update(self.get_option_values()) | ||||
| 
 | ||||
|         if isinstance(self._connection, NetworkCli): | ||||
|             result['network_api'] = 'cliconf' | ||||
|         elif isinstance(self._connection, HttpApi): | ||||
|             result['network_api'] = 'nxapi' | ||||
|         else: | ||||
|             raise ValueError("Invalid connection type") | ||||
|         return json.dumps(result) | ||||
| 
 | ||||
|     def _get_command_with_output(self, command, output): | ||||
|         options_values = self.get_option_values() | ||||
|         if output not in options_values['output']: | ||||
|             raise ValueError("'output' value %s is invalid. Valid values are %s" % (output, ','.join(options_values['output']))) | ||||
| 
 | ||||
|         if output == 'json' and not command.endswith('| json'): | ||||
|             cmd = '%s | json' % command | ||||
|         elif output == 'text' and command.endswith('| json'): | ||||
|             cmd = command.rsplit('|', 1)[0] | ||||
|         else: | ||||
|             cmd = command | ||||
|         return cmd | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue