コード例 #1
0
    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
コード例 #2
0
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"
    )
コード例 #3
0
    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)))
コード例 #4
0
ファイル: helper.py プロジェクト: me-kashif/scrapli
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 []
コード例 #5
0
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
コード例 #6
0
    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}
コード例 #7
0
ファイル: pyats.py プロジェクト: ciscops/ansible-devops
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
コード例 #8
0
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)
コード例 #9
0
        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:
コード例 #10
0
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