def genie_parser(self, cli_output, command, os): if not PY3: raise AnsibleFilterError("Genie requires Python 3") if not HAS_GENIE: raise AnsibleFilterError( "Genie not found. Run 'pip install genie'") if not HAS_PYATS: raise AnsibleFilterError( "pyATS not found. Run 'pip install pyats'") device = Device("new_device", os=os) device.custom.setdefault("abstraction", {})["order"] = ["os"] device.cli = AttrDict({"execute": None}) try: get_parser(command, device) except Exception as e: raise AnsibleFilterError( "Unable to find parser for command '{0}' ({1})".format( command, e)) try: parsed_output = device.parse(command, output=cli_output) except Exception as e: raise AnsibleFilterError( "Unable to parse output for command '{0}' ({1})".format( command, e)) if parsed_output: return parsed_output else: return None
def main(): """" Simple script which loads the text output of a show command from a variable and uses the Genie parsing engine to parse the output. This shows how to use the Genie parsers with content obtained elsewhere, - a variable - a text file - output from some other means """ # Because we are using the parsing engine without a testbed file and with static content # This creates a device model for parsing device = Device(name='csr1kv', os='iosxe') device.custom.abstraction = {'order': ['os']} print("Loading show command output...") response = load_data() print(f"\n==== show command output form variable is: \b{response}") print( f"\n=================== DEVICE PARSING {device.name} ===================" ) parsed_output = device.parse('show version', output=response) print( f"\nParsed Output in a {type(parsed_output)}: \n{json.dumps(parsed_output, indent=4)}" ) print( f"\n=================== END DEVICE PARSING {device.name} ===================\n" )
def _parse(raw_cli_output, cmd, nos): # Boilerplate code to get the parser functional # tb = Testbed() device = Device("new_device", os=nos) device.custom.setdefault("abstraction", {})["order"] = ["os"] device.cli = AttrDict({"execute": None}) # User input checking of the command provided. Does the command have a Genie parser? try: get_parser(cmd, device) except Exception as e: raise AnsibleFilterError( "genie_parse: {0} - Available parsers: {1}".format( to_native(e), "https://pubhub.devnetcloud.com/media/pyats-packages/docs/genie/genie_libs/#/parsers" )) try: parsed_output = device.parse(cmd, output=raw_cli_output) return parsed_output except Exception as e: raise AnsibleFilterError( "genie_parse: {0} - Failed to parse command output.".format( to_native(e)))
def genie_parse(platform: str, command: str, output: str) -> Union[List[Any], Dict[str, Any]]: """ Parse output with Cisco genie parsers, try to return structured output Args: platform: genie device type; i.e. iosxe, iosxr, etc. command: string of command that was executed (to find appropriate parser) output: unstructured output from device to parse Returns: output: structured data Raises: N/A """ try: from genie.conf.base import Device # pylint: disable=C0415 from genie.libs.parser.utils import get_parser # pylint: disable=C0415 except ModuleNotFoundError as exc: err = f"Module '{exc.name}' not installed!" msg = f"***** {err} {'*' * (80 - len(err))}" fix = ( f"To resolve this issue, install '{exc.name}'. You can do this in one of the following" " ways:\n" "1: 'pip install -r requirements-genie.txt'\n" "2: 'pip install scrapli[genie]'") warning = "\n" + msg + "\n" + fix + "\n" + msg warnings.warn(warning) return [] genie_device = Device("scrapli_device", custom={"abstraction": { "order": ["os"] }}, os=platform) try: get_parser(command, genie_device) genie_parsed_result = genie_device.parse(command, output=output) if not genie_parsed_result: return [] if isinstance(genie_parsed_result, (list, dict)): return genie_parsed_result # have to catch base exception because that is all genie raises for some reason :( except Exception: # pylint: disable=E0012,W0703 pass return []
def get_structured_data_genie(raw_output: str, platform: str, command: str) -> Union[str, Dict[str, Any]]: if not sys.version_info >= (3, 4): raise ValueError("Genie requires Python >= 3.4") if not GENIE_INSTALLED: msg = ( "\nGenie and PyATS are not installed. Please PIP install both Genie and PyATS:\n" "pip install genie\npip install pyats\n") raise ValueError(msg) if "cisco" not in platform: return raw_output genie_device_mapper = { "cisco_ios": "ios", "cisco_xe": "iosxe", "cisco_xr": "iosxr", "cisco_nxos": "nxos", "cisco_asa": "asa", } os = None # platform might be _ssh, _telnet, _serial strip that off if platform.count("_") > 1: base_list = platform.split("_")[:-1] base_platform = "_".join(base_list) else: base_platform = platform os = genie_device_mapper.get(base_platform) if os is None: return raw_output # Genie specific construct for doing parsing (based on Genie in Ansible) device = Device("new_device", os=os) device.custom.setdefault("abstraction", {}) device.custom["abstraction"]["order"] = ["os"] device.cli = AttrDict({"execute": None}) try: # Test whether there is a parser for given command (return Exception if fails) get_parser(command, device) parsed_output: Dict[str, Any] = device.parse(command, output=raw_output) return parsed_output except Exception: return raw_output
def parse(self, *_args, **_kwargs): """Std entry point for a cli_parse parse execution :return: Errors or parsed text as structured data :rtype: dict :example: The parse function of a parser should return a dict: {"errors": [a list of errors]} or {"parsed": obj} """ errors = self._check_reqs() errors.extend(self._check_vars()) if errors: return {"errors": errors} command = self._task_args.get("parser").get("command") network_os = ( self._task_args.get("parser").get("os") or self._transform_ansible_network_os() ) cli_output = self._task_args.get("text") device = Device("new_device", os=network_os) device.custom.setdefault("abstraction", {})["order"] = ["os"] device.cli = AttrDict({"execute": None}) try: parsed = device.parse(command, output=cli_output) except Exception as exc: msg = "The pyats library return an error for '{cmd}' for '{os}'. Error: {err}." return { "errors": [ ( msg.format( cmd=command, os=network_os, err=to_native(exc) ) ) ] } return {"parsed": parsed}
def pyats_parser(cli_output, command, os): if not PY3: raise AnsibleFilterError("Genie requires Python 3") if GENIE_IMPORT_ERROR: raise_from( AnsibleError('genie must be installed to use this plugin'), GENIE_IMPORT_ERROR) if PYATS_IMPORT_ERROR: raise_from( AnsibleError('pyats must be installed to use this plugin'), PYATS_IMPORT_ERROR) # Translate from ansible_network_os values to pyATS if os in ansible_os_map.keys(): os = ansible_os_map[os] device = Device("uut", os=os) device.custom.setdefault("abstraction", {})["order"] = ["os"] device.cli = AttrDict({"execute": None}) try: get_parser(command, device) except Exception as e: raise AnsibleFilterError("Unable to find parser for command '{0}' ({1})".format(command, e)) try: parsed_output = device.parse(command, output=cli_output) except Exception as e: raise AnsibleFilterError("Unable to parse output for command '{0}' ({1})".format(command, e)) if parsed_output: return parsed_output else: return None
def main(): argument_spec = dict(command=dict(type='str', required=True), prompt=dict(type='list', required=False), answer=dict(type='list', required=False), compare=dict(type='dict', required=False), sendonly=dict(type='bool', default=False, required=False), # newline=dict(type='bool', default=True, required=False), # check_all=dict(type='bool', default=False, required=False), ) required_together = [['prompt', 'answer']] module = AnsibleModule(argument_spec=argument_spec, required_together=required_together, supports_check_mode=True) if not PY3: module.fail_json(msg="pyATS/Genie requires Python 3") if not HAS_GENIE: module.fail_json(msg="Genie not found. Run 'pip install genie'") if not HAS_PYATS: module.fail_json(msg="pyATS not found. Run 'pip install pyats'") if module.check_mode and not module.params['command'].startswith('show'): module.fail_json( msg='Only show commands are supported when using check_mode, not ' 'executing %s' % module.params['command'] ) warnings = list() result = {'changed': False, 'warnings': warnings} connection = Connection(module._socket_path) capabilities = json.loads(connection.get_capabilities()) if capabilities['device_info']['network_os'] == 'ios': genie_os = 'iosxe' else: genie_os = capabilities['device_info']['network_os'] compare = module.params.pop('compare') response = '' try: response = connection.get(**module.params) except ConnectionError as exc: module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) device = Device("uut", os=genie_os) device.custom.setdefault("abstraction", {})["order"] = ["os"] device.cli = AttrDict({"execute": None}) try: get_parser(module.params['command'], device) except Exception as e: module.fail_json(msg="Unable to find parser for command '{0}' ({1})".format(module.params['command'], e)) try: parsed_output = device.parse(module.params['command'], output=response) except Exception as e: module.fail_json(msg="Unable to parse output for command '{0}' ({1})".format(module.params['command'], e)) # import sys; # sys.stdin = open('/dev/tty') # import pdb; # pdb.set_trace() if compare: diff = Diff(parsed_output, compare, exclude=get_parser_exclude(module.params['command'], device)) diff.findDiff() else: diff = None if not module.params['sendonly']: try: result['json'] = module.from_json(response) except ValueError: pass result.update({ 'stdout': response, 'structured': parsed_output, 'diff': "{0}".format(diff), 'exclude': get_parser_exclude(module.params['command'], device), }) module.exit_json(**result)
command = command[:-8] indx = command.find("show") device_name = filename[:indx] command = command[indx:] device_name = device_name.replace("pre_", "", 1) device_name = device_name.replace("post_", "", 1) command = command.replace("_", " ") with open(pre_filename, newline='') as f: # Open show command list output = f.read() if isBlank(output) == False: output = dev.parse(command, output=output) pre = len(output["vrf"]["default"]["neighbor"]) preNeighbors = getBGPneighbors(output) filecount += 1 else: continue print('=============================================================') print('===================== ' + device_name + ' tests ===============') with open(post_filename, newline='') as f: output = f.read() if isBlank(output) == False:
class ActionModule(ActionBase): """ action module """ def __init__(self, *args, **kwargs): super(ActionModule, self).__init__(*args, **kwargs) self._cmd_dict = None self._errors = [] self._facts = {} self._filters = {} self._ignore_parser_errors = False self._network_os = None self._commands = None self._module_name = None self._engine = None self._pyats_device = None self._result = None self._result_details = [] self._templar_filters = Templar(loader=None).environment.filters def _add_facts(self, parsed): if isinstance(parsed, list): for chunk in parsed: self._facts = dict_merge(self._facts, chunk) elif isinstance(parsed, dict): for k, val in parsed.items(): if k in self._facts: # pylint: disable=C0123 if type(self._facts[k]) != type(val): message = ("Cannot merge '{}' and '{}'" " for facts key '{fkey}'. Ensure" " all values for '{fkey}' are of" " the same type".format(type( self._facts[k]).__name__, type(val).__name__, fkey=k)) raise AnsibleError(message) self._facts = dict_merge(self._facts, parsed) def _check_argspec(self): # pylint: disable=W0212 basic._ANSIBLE_ARGS = to_bytes( json.dumps({'ANSIBLE_MODULE_ARGS': self._task.args})) # pylint: enable=W0212 spec = {k: v for k, v in ARGSPEC.items() if k in VALID_MODULE_KWARGS} basic.AnsibleModule.fail_json = self._fail_json basic.AnsibleModule(**spec) def _check_engine(self): if self._engine == "pyats": if not PY3: self._errors.append("Genie requires Python 3") if not HAS_GENIE: self._errors.append("Genie not found. Run 'pip install genie'") if not HAS_PYATS: self._errors.append("pyATS not found. Run 'pip install pyats'") elif self._engine == "native_xml": if not HAS_XMLTODICT: self._errors.append("xmltodict not found." " Run 'pip install xmltodict'") def _check_network_os(self): if not self._network_os: self._errors.append("'network_os' must be provided or" "ansible_network_os set for this host") def _check_commands_against_pyats(self): network_os = self._task.args.get('network_os') or self._network_os self._pyats_device = Device("uut", os=network_os) self._pyats_device.custom.setdefault("abstraction", {})["order"] = ["os"] self._pyats_device.cli = AttrDict({"execute": None}) for command in self._commands: try: get_parser(command['command'], self._pyats_device) except Exception: # pylint: disable=W0703 self._errors.append("PYATS: Unable to find parser for command " "'{}' for {}".format( command['command'], network_os)) self._check_for_errors() def _check_for_errors(self): if self._errors: raise AnsibleError(". ".join(self._errors)) def _check_module_name(self): if self._module_name not in self._shared_loader_obj.module_loader: self._errors.append("{} is not supported".format(self._network_os)) self._check_for_errors() def _check_transforms(self): for command in self._commands: for transform in command.get('transform', []): if 'name' not in transform: self._errors.append( "Transform '{}' missing `name` in command '{}'".format( transform['name'], command['command'])) continue if not self._filters.get(transform['name']): self._errors.append( "Transform '{}' not found in command '{}'".format( transform['name'], command['command'])) def _fail_json(self, msg): msg = msg.replace('(basic.py)', self._task.action) raise AnsibleModuleError(msg) def _load_filters(self): filter_loader = getattr(self._shared_loader_obj, 'filter_loader') for fpl in filter_loader.all(): self._filters.update(fpl.filters()) for command in self._commands: for transform in command.get('transform', []): if 'name' not in transform: self._errors.append( "Transform '{}' missing `name` in command '{}'".format( transform['name'], command['command'])) continue if hasattr(self._task, 'collections'): for collection in self._task.collections or []: full_name = "{}.{}".format(collection, transform['name']) filterfn = self._templar_filters.get(full_name) if filterfn: self._filters[transform['name']] = filterfn break if transform['name'] not in self._filters: full_name = "{}.{}".format('nmake.jetpack', transform['name']) filterfn = self._templar_filters.get(full_name) if filterfn: self._filters[transform['name']] = filterfn def _set_send_commands(self): if self._engine == 'native_json': if self._network_os == "junos": append_json = " | display json" else: append_json = " | json" for command in self._commands: command['command'] += append_json elif self._engine == 'native_xml': append_xml = " | xmlout" for command in self._commands: command['command'] += append_xml def _parse_stdout(self): for command in self._commands: stdout = self._cmd_dict.get(command['command']) entry = {"command": command['command']} try: if self._engine == 'pyats': parsed = self._pyats_device.parse(command['command'], output=stdout) elif self._engine == 'native_json': if isinstance(stdout, str): parsed = {} else: parsed = stdout elif self._engine == 'native_xml': if not stdout: parsed = {} else: splitted = stdout.splitlines() if splitted[-1] == ']]>]]>': stdout = '\n'.join(splitted[:-1]) parsed = xmltodict.parse(stdout) except Exception as err: # pylint: disable=W0703 msg = ("{}: Unable to parse output for command '{}' for {}". format(self._engine.upper(), command['command'], self._network_os)) if self._ignore_parser_errors: display.warning(msg) parsed = {} else: self._errors.append(msg) self._check_for_errors() parsed = self._run_transforms(command, parsed) entry['parsed'] = parsed self._result_details.append(entry) if command.get('set_fact'): self._add_facts(parsed) def _run_commands(self): commands = list(set(command['command'] for command in self._commands)) new_module_args = {"commands": commands} res = self._execute_module(module_name=self._module_name, module_args=new_module_args, task_vars={}, tmp=None) if res.get('failed'): raise AnsibleError("Failure while running command on" " device: {}".format(res['msg'])) self._cmd_dict = { c: res['stdout'][idx] for idx, c in enumerate(commands) } def _run_transforms(self, command, parsed): for transform in command.get('transform', []): filterfn = self._filters.get(transform['name']) del transform['name'] parsed = filterfn(parsed, **transform) return parsed def _set_vars(self, task_vars): self._network_os = task_vars.get('ansible_network_os') self._commands = self._task.args.get('commands') self._module_name = '{}_command'.format(self._network_os) self._engine = self._task.args.get('engine') self._ignore_parser_errors = self._task.args.get( 'ignore_parser_errors') def run(self, tmp=None, task_vars=None): # pylint: disable=W0212 self._result = super(ActionModule, self).run(tmp, task_vars) self._check_argspec() self._set_vars(task_vars) self._check_engine() self._check_network_os() self._check_for_errors() self._load_filters() self._check_transforms() self._check_for_errors() self._check_module_name() if self._engine == 'pyats': self._check_commands_against_pyats() self._check_for_errors() self._set_send_commands() self._run_commands() self._parse_stdout() current_facts = task_vars.get('vars', {}).get('ansible_facts', {}) new_facts = dict_merge(current_facts, self._facts) self._result.update({ 'ansible_facts': new_facts, 'details': self._result_details }) return self._result