diff options
| author | ansible-zuul[bot] <48994755+ansible-zuul[bot]@users.noreply.github.com> | 2020-03-02 17:06:32 +0000 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-03-02 17:06:32 +0000 | 
| commit | 1b812d72c2f7076229404891724b56bfc8a56312 (patch) | |
| tree | 2cd790e3470c8cd13502e5dbff47c9b3dfb2cf8c /plugins/module_utils/network | |
| parent | 4313b070205766e68d30cea4f49a6bad83007bb0 (diff) | |
| parent | a38aeadb72d2a6aef8510ca535060add98fccc3b (diff) | |
| download | vyos.vyos-1b812d72c2f7076229404891724b56bfc8a56312.tar.gz vyos.vyos-1b812d72c2f7076229404891724b56bfc8a56312.zip | |
Merge pull request #5 from CaptTrews/master
Updated from network content collector
Diffstat (limited to 'plugins/module_utils/network')
8 files changed, 745 insertions, 3 deletions
| diff --git a/plugins/module_utils/network/vyos/argspec/firewall_interfaces/__init__.py b/plugins/module_utils/network/vyos/argspec/firewall_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/firewall_interfaces/__init__.py diff --git a/plugins/module_utils/network/vyos/argspec/firewall_interfaces/firewall_interfaces.py b/plugins/module_utils/network/vyos/argspec/firewall_interfaces/firewall_interfaces.py new file mode 100644 index 00000000..f0834850 --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/firewall_interfaces/firewall_interfaces.py @@ -0,0 +1,85 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +#                WARNING                    # +############################################# +# +# This file is auto generated by the resource +#   module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +#   by the resource module builder. +# +# Changes should be made in the model used to +#   generate this file or in the resource module +#   builder template. +# +############################################# +""" +The arg spec for the vyos_firewall_interfaces module +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +class Firewall_interfacesArgs(object):  # pylint: disable=R0903 +    """The arg spec for the vyos_firewall_interfaces module +    """ + +    def __init__(self, **kwargs): +        pass + +    argument_spec = { +        "config": { +            "elements": "dict", +            "options": { +                "access_rules": { +                    "elements": "dict", +                    "options": { +                        "afi": { +                            "choices": ["ipv4", "ipv6"], +                            "required": True, +                            "type": "str", +                        }, +                        "rules": { +                            "elements": "dict", +                            "options": { +                                "direction": { +                                    "choices": ["in", "local", "out"], +                                    "required": True, +                                    "type": "str", +                                }, +                                "name": {"type": "str"}, +                            }, +                            "type": "list", +                        }, +                    }, +                    "type": "list", +                }, +                "name": {"required": True, "type": "str"}, +            }, +            "type": "list", +        }, +        "running_config": {"type": "str"}, +        "state": { +            "choices": [ +                "merged", +                "replaced", +                "overridden", +                "deleted", +                "parsed", +                "rendered", +                "gathered", +            ], +            "default": "merged", +            "type": "str", +        }, +    }  # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/config/firewall_interfaces/__init__.py b/plugins/module_utils/network/vyos/config/firewall_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/plugins/module_utils/network/vyos/config/firewall_interfaces/__init__.py diff --git a/plugins/module_utils/network/vyos/config/firewall_interfaces/firewall_interfaces.py b/plugins/module_utils/network/vyos/config/firewall_interfaces/firewall_interfaces.py new file mode 100644 index 00000000..b436bfad --- /dev/null +++ b/plugins/module_utils/network/vyos/config/firewall_interfaces/firewall_interfaces.py @@ -0,0 +1,455 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The vyos_firewall_interfaces class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from copy import deepcopy +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( +    ConfigBase, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( +    to_list, +    dict_diff, +    remove_empties, +    search_obj_in_list, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import ( +    Facts, +) + + +class Firewall_interfaces(ConfigBase): +    """ +    The vyos_firewall_interfaces class +    """ + +    gather_subset = [ +        "!all", +        "!min", +    ] + +    gather_network_resources = [ +        "firewall_interfaces", +    ] + +    def __init__(self, module): +        super(Firewall_interfaces, self).__init__(module) + +    def get_firewall_interfaces_facts(self, data=None): +        """ Get the 'facts' (the current configuration) + +        :rtype: A dictionary +        :returns: The current configuration as a dictionary +        """ +        facts, _warnings = Facts(self._module).get_facts( +            self.gather_subset, self.gather_network_resources, data=data +        ) +        firewall_interfaces_facts = facts["ansible_network_resources"].get( +            "firewall_interfaces" +        ) +        if not firewall_interfaces_facts: +            return [] +        return firewall_interfaces_facts + +    def execute_module(self): +        """ Execute the module + +        :rtype: A dictionary +        :returns: The result from module execution +        """ +        result = {"changed": False} +        warnings = list() +        commands = list() + +        if self.state in self.ACTION_STATES: +            existing_firewall_interfaces_facts = ( +                self.get_firewall_interfaces_facts() +            ) +        else: +            existing_firewall_interfaces_facts = [] + +        if self.state in self.ACTION_STATES or self.state == "rendered": +            commands.extend( +                self.set_config(existing_firewall_interfaces_facts) +            ) + +        if commands and self.state in self.ACTION_STATES: +            if not self._module.check_mode: +                self._connection.edit_config(commands) +            result["changed"] = True + +        if self.state in self.ACTION_STATES: +            result["commands"] = commands + +        if self.state in self.ACTION_STATES or self.state == "gathered": +            changed_firewall_interfaces_facts = ( +                self.get_firewall_interfaces_facts() +            ) +        elif self.state == "rendered": +            result["rendered"] = commands +        elif self.state == "parsed": +            running_config = self._module.params["running_config"] +            if not running_config: +                self._module.fail_json( +                    msg="value of running_config parameter must not be empty for state parsed" +                ) +            result["parsed"] = self.get_firewall_interfaces_facts( +                data=running_config +            ) +        else: +            changed_firewall_interfaces_facts = [] + +        if self.state in self.ACTION_STATES: +            result["before"] = existing_firewall_interfaces_facts +            if result["changed"]: +                result["after"] = changed_firewall_interfaces_facts +        elif self.state == "gathered": +            result["gathered"] = changed_firewall_interfaces_facts + +        result["warnings"] = warnings +        return result + +    def set_config(self, existing_firewall_interfaces_facts): +        """ Collect the configuration from the args passed to the module, +            collect the current configuration (as a dict from facts) + +        :rtype: A list +        :returns: the commands necessary to migrate the current configuration +                  to the desired configuration +        """ +        want = self._module.params["config"] +        have = existing_firewall_interfaces_facts +        resp = self.set_state(want, have) +        return to_list(resp) + +    def set_state(self, w, h): +        """ Select the appropriate function based on the state provided + +        :param want: the desired configuration as a dictionary +        :param have: the current configuration as a dictionary +        :rtype: A list +        :returns: the commands necessary to migrate the current configuration +                  to the desired configuration +        """ +        commands = [] +        if ( +            self.state in ("merged", "replaced", "overridden", "rendered") +            and not w +        ): +            self._module.fail_json( +                msg="value of config parameter must not be empty for state {0}".format( +                    self.state +                ) +            ) +        if self.state == "overridden": +            commands.extend(self._state_overridden(w, h)) +        elif self.state == "deleted": +            commands.extend(self._state_deleted(w, h)) +        elif w: +            if self.state == "merged" or self.state == "rendered": +                commands.extend(self._state_merged(w, h)) +            elif self.state == "replaced": +                commands.extend(self._state_replaced(w, h)) +        return commands + +    def _state_replaced(self, want, have): +        """ The command generator when state is replaced + +        :rtype: A list +        :returns: the commands necessary to migrate the current configuration +                  to the desired configuration +        """ +        commands = [] +        if have: +            for h in have: +                w = search_obj_in_list(h["name"], want) +                commands.extend(self._render_access_rules(h, w, opr=False)) +        commands.extend(self._state_merged(want, have)) +        return commands + +    def _state_overridden(self, want, have): +        """ The command generator when state is overridden + +        :rtype: A list +        :returns: the commands necessary to migrate the current configuration +                  to the desired configuration +        """ +        commands = [] +        if have: +            for h_ar in have: +                w_ar = search_obj_in_list(h_ar["name"], want) +                if not w_ar and "access_rules" in h_ar: +                    commands.append( +                        self._compute_command(name=h_ar["name"], opr=False) +                    ) +                else: +                    h_rules = h_ar.get("access_rules") or [] +                    key = "direction" +                    if w_ar: +                        w_rules = w_ar.get("access_rules") or [] +                        if not w_rules and h_rules: +                            commands.append( +                                self._compute_command( +                                    name=h_ar["name"], opr=False +                                ) +                            ) +                    if h_rules: +                        for h_rule in h_rules: +                            w_rule = search_obj_in_list( +                                h_rule["afi"], w_rules, key="afi" +                            ) +                            have_rules = h_rule.get("rules") or [] +                            if w_rule: +                                want_rules = w_rule.get("rules") or [] +                            for h in have_rules: +                                if key in h: +                                    w = search_obj_in_list( +                                        h[key], want_rules, key=key +                                    ) +                                    if ( +                                        not w +                                        or key not in w +                                        or ( +                                            "name" in h +                                            and w +                                            and "name" not in w +                                        ) +                                    ): +                                        commands.append( +                                            self._compute_command( +                                                afi=h_rule["afi"], +                                                name=h_ar["name"], +                                                attrib=h[key], +                                                opr=False, +                                            ) +                                        ) + +        commands.extend(self._state_merged(want, have)) +        return commands + +    def _state_merged(self, want, have): +        """ The command generator when state is merged + +        :rtype: A list +        :returns: the commands necessary to merge the provided into +                  the current configuration +        """ +        commands = [] +        for w in want: +            h = search_obj_in_list(w["name"], have) +            commands.extend(self._render_access_rules(w, h)) +        return commands + +    def _state_deleted(self, want, have): +        """ The command generator when state is deleted + +        :rtype: A list +        :returns: the commands necessary to remove the current configuration +                  of the provided objects +        """ +        commands = [] + +        if want: +            for w in want: +                h = search_obj_in_list(w["name"], have) +                if h and "access_rules" in h: +                    commands.extend(self._delete_access_rules(w, h, opr=False)) +        elif have: +            for h in have: +                if "access_rules" in h: +                    commands.append( +                        self._compute_command(name=h["name"], opr=False) +                    ) +        return commands + +    def _delete_access_rules(self, want, have, opr=False): +        """ +        This function forms the delete commands based on the 'opr' type +        for 'access_rules' attributes. +        :param want: desired config. +        :param have: target config. +        :param opr: True/False. +        :return: generated commands list. +        """ +        commands = [] +        h_rules = {} +        w_rs = deepcopy(remove_empties(want)) +        w_rules = w_rs.get("access_rules") or [] +        if have: +            h_rs = deepcopy(remove_empties(have)) +            h_rules = h_rs.get("access_rules") or [] + +        # if all firewall config needed to be deleted for specific interface +        # when operation is delete. +        if not w_rules and h_rules: +            commands.append(self._compute_command(name=want["name"], opr=opr)) +        if w_rules: +            for w in w_rules: +                h = search_obj_in_list(w["afi"], h_rules, key="afi") +                commands.extend(self._delete_rules(want["name"], w, h)) +        return commands + +    def _delete_rules(self, name, want, have, opr=False): +        """ +        This function forms the delete commands based on the 'opr' type +        for rules attributes. +        :param name: interface id/name. +        :param want: desired config. +        :param have: target config. +        :param opr: True/False. +        :return: generated commands list. +        """ +        commands = [] +        h_rules = [] +        key = "direction" +        w_rules = want.get("rules") or [] +        if have: +            h_rules = have.get("rules") or [] +        # when rule set needed to be removed on +        # (inbound|outbound|local interface) +        if h_rules and not w_rules: +            for h in h_rules: +                if key in h: +                    commands.append( +                        self._compute_command( +                            afi=want["afi"], name=name, attrib=h[key], opr=opr +                        ) +                    ) +        for w in w_rules: +            h = search_obj_in_list(w[key], h_rules, key=key) +            if ( +                key in w +                and h +                and key in h +                and "name" in w +                and "name" in h +                and w["name"] == h["name"] +            ): +                commands.append( +                    self._compute_command( +                        afi=want["afi"], +                        name=name, +                        attrib=w[key], +                        value=w["name"], +                        opr=opr, +                    ) +                ) +        return commands + +    def _render_access_rules(self, want, have, opr=True): +        """ +        This function forms the set/delete commands based on the 'opr' type +        for 'access_rules' attributes. +        :param want: desired config. +        :param have: target config. +        :param opr: True/False. +        :return: generated commands list. +        """ +        commands = [] +        h_rules = {} +        w_rs = deepcopy(remove_empties(want)) +        w_rules = w_rs.get("access_rules") or [] +        if have: +            h_rs = deepcopy(remove_empties(have)) +            h_rules = h_rs.get("access_rules") or [] +        if w_rules: +            for w in w_rules: +                h = search_obj_in_list(w["afi"], h_rules, key="afi") +                commands.extend(self._render_rules(want["name"], w, h, opr)) +        return commands + +    def _render_rules(self, name, want, have, opr=True): +        """ +        This function forms the set/delete commands based on the 'opr' type +        for rules attributes. +        :param name: interface id/name. +        :param want: desired config. +        :param have: target config. +        :param opr: True/False. +        :return: generated commands list. +        """ +        commands = [] +        h_rules = [] +        key = "direction" +        w_rules = want.get("rules") or [] +        if have: +            h_rules = have.get("rules") or [] +        for w in w_rules: +            h = search_obj_in_list(w[key], h_rules, key=key) +            if key in w: +                if opr: +                    if "name" in w and not ( +                        h and h[key] == w[key] and h["name"] == w["name"] +                    ): +                        commands.append( +                            self._compute_command( +                                afi=want["afi"], +                                name=name, +                                attrib=w[key], +                                value=w["name"], +                            ) +                        ) +                    elif not (h and key in h): +                        commands.append( +                            self._compute_command( +                                afi=want["afi"], name=name, attrib=w[key] +                            ) +                        ) +                elif not opr: +                    if ( +                        not h +                        or key not in h +                        or ("name" in w and h and "name" not in h) +                    ): +                        commands.append( +                            self._compute_command( +                                afi=want["afi"], +                                name=name, +                                attrib=w[key], +                                opr=opr, +                            ) +                        ) +        return commands + +    def _compute_command( +        self, afi=None, name=None, attrib=None, value=None, opr=True +    ): +        """ +        This function construct the add/delete command based on passed attributes. +        :param afi:  address type. +        :param name: interface name. +        :param attrib: attribute name. +        :param value: attribute value. +        :param opr: operation flag. +        :return: generated command. +        """ +        if not opr: +            cmd = "delete interfaces ethernet" + " " + name + " firewall" +        else: +            cmd = "set interfaces ethernet" + " " + name + " firewall" +        if attrib: +            cmd += " " + attrib +        if afi: +            cmd += " " + self._get_fw_type(afi) +        if value: +            cmd += " '" + str(value) + "'" +        return cmd + +    def _get_fw_type(self, afi): +        """ +        This function returns the firewall rule-set type based on IP address. +        :param afi: address type +        :return: rule-set type. +        """ +        return "ipv6-name" if afi == "ipv6" else "name" diff --git a/plugins/module_utils/network/vyos/facts/facts.py b/plugins/module_utils/network/vyos/facts/facts.py index 6e6a82be..ff3d0988 100644 --- a/plugins/module_utils/network/vyos/facts/facts.py +++ b/plugins/module_utils/network/vyos/facts/facts.py @@ -9,6 +9,7 @@ calls the appropriate facts gathering function  from __future__ import absolute_import, division, print_function  __metaclass__ = type +  from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts import (      FactsBase,  ) @@ -36,6 +37,9 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.stati  from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_global.firewall_global import (      Firewall_globalFacts,  ) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_interfaces.firewall_interfaces import ( +    Firewall_interfacesFacts, +)  from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.legacy.base import (      Default,      Neighbors, @@ -53,6 +57,7 @@ FACT_RESOURCE_SUBSETS = dict(      static_routes=Static_routesFacts,      firewall_rules=Firewall_rulesFacts,      firewall_global=Firewall_globalFacts, +    firewall_interfaces=Firewall_interfacesFacts,  ) diff --git a/plugins/module_utils/network/vyos/facts/firewall_interfaces/__init__.py b/plugins/module_utils/network/vyos/facts/firewall_interfaces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/firewall_interfaces/__init__.py diff --git a/plugins/module_utils/network/vyos/facts/firewall_interfaces/firewall_interfaces.py b/plugins/module_utils/network/vyos/facts/firewall_interfaces/firewall_interfaces.py new file mode 100644 index 00000000..46407486 --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/firewall_interfaces/firewall_interfaces.py @@ -0,0 +1,196 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The vyos firewall_interfaces fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from re import findall, search, M +from copy import deepcopy +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( +    utils, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.firewall_interfaces.firewall_interfaces import ( +    Firewall_interfacesArgs, +) + + +class Firewall_interfacesFacts(object): +    """ The vyos firewall_interfaces fact class +    """ + +    def __init__(self, module, subspec="config", options="options"): +        self._module = module +        self.argument_spec = Firewall_interfacesArgs.argument_spec +        spec = deepcopy(self.argument_spec) +        if subspec: +            if options: +                facts_argument_spec = spec[subspec][options] +            else: +                facts_argument_spec = spec[subspec] +        else: +            facts_argument_spec = spec + +        self.generated_spec = utils.generate_dict(facts_argument_spec) + +    def get_device_data(self, connection): +        return connection.get_config() + +    def populate_facts(self, connection, ansible_facts, data=None): +        """ Populate the facts for firewall_interfaces +        :param connection: the device connection +        :param ansible_facts: Facts dictionary +        :param data: previously collected conf +        :rtype: dictionary +        :returns: facts +        """ +        if not data: +            # typically data is populated from the current device configuration +            # data = connection.get('show running-config | section ^interface') +            # using mock data instead +            data = self.get_device_data(connection) +        objs = [] +        interfaces = findall( +            r"^set interfaces ethernet (?:\'*)(\S+)(?:\'*)", data, M +        ) +        if interfaces: +            objs = self.get_names(data, interfaces) +        ansible_facts["ansible_network_resources"].pop( +            "firewall_interfaces", None +        ) +        facts = {} +        if objs: +            facts["firewall_interfaces"] = [] +            params = utils.validate_config( +                self.argument_spec, {"config": objs} +            ) +            for cfg in params["config"]: +                facts["firewall_interfaces"].append(utils.remove_empties(cfg)) + +        ansible_facts["ansible_network_resources"].update(facts) +        return ansible_facts + +    def get_names(self, data, interfaces): +        """ +        This function performs following: +        - Form regex to fetch 'interface name' from  interfaces firewall data. +        - Form the name list. +        :param data: configuration. +        :param rules: list of interfaces. +        :return: generated firewall interfaces configuration. +        """ +        names = [] +        for r in set(interfaces): +            int_regex = r" %s .+$" % r.strip("'") +            cfg = findall(int_regex, data, M) +            fi = self.render_config(cfg) +            fi["name"] = r.strip("'") +            names.append(fi) +        if names: +            names = sorted(names, key=lambda i: i["name"]) +        return names + +    def render_config(self, conf): +        """ +        Render config as dictionary structure and delete keys +          from spec for null values + +        :param spec: The facts tree, generated from the argspec +        :param conf: The configuration +        :rtype: dictionary +        :returns: The generated config +        """ +        conf = "\n".join(filter(lambda x: "firewall" in x, conf)) +        config = {"access_rules": self.parse_access_rules(conf)} +        return config + +    def parse_access_rules(self, conf): +        """ +        This function forms the regex to fetch the 'access-rules' +        for specific interface. +        :param conf: configuration data. +        :return: generated access-rules list configuration. +        """ +        ar_lst = [] +        v4_ar = findall(r"^.*(in|out|local) name .*$", conf, M) +        v6_ar = findall(r"^.*(in|out|local) ipv6-name .*$", conf, M) +        if v4_ar: +            v4_conf = "\n".join(findall(r"(^.*?%s.*?$)" % " name", conf, M)) +            config = self.parse_int_rules(v4_conf, "ipv4") +            if config: +                ar_lst.append(config) +        if v6_ar: +            v6_conf = "\n".join( +                findall(r"(^.*?%s.*?$)" % " ipv6-name", conf, M) +            ) +            config = self.parse_int_rules(v6_conf, "ipv6") +            if config: +                ar_lst.append(config) +        if ar_lst: +            ar_lst = sorted(ar_lst, key=lambda i: i["afi"]) +        else: +            empty_rules = findall(r"^.*(in|out|local).*", conf, M) +            if empty_rules: +                ar_lst.append({"afi": "ipv4", "rules": []}) +                ar_lst.append({"afi": "ipv6", "rules": []}) +        return ar_lst + +    def parse_int_rules(self, conf, afi): +        """ +        This function forms the regex to fetch the 'access-rules' +        for specific interface based on ip-type. +        :param conf: configuration data. +        :param rules: rules configured per interface. +        :param afi: ip address type. +        :return: generated rule configuration dictionary. +        """ +        r_lst = [] +        config = {} +        rules = ["in", "out", "local"] +        for r in set(rules): +            fr = {} +            r_regex = r" %s .+$" % r +            cfg = "\n".join(findall(r_regex, conf, M)) +            if cfg: +                fr = self.parse_rules(cfg, afi) +            else: +                out = search(r"^.*firewall " + "'" + r + "'" + "(.*)", conf, M) +                if out: +                    fr = {"direction": r} +            if fr: +                r_lst.append(fr) +        if r_lst: +            r_lst = sorted(r_lst, key=lambda i: i["direction"]) +            config = {"afi": afi, "rules": r_lst} +        return config + +    def parse_rules(self, conf, afi): +        """ +        This function triggers the parsing of 'rule' attributes. +        a_lst is a list having rule attributes which doesn't +        have further sub attributes. +        :param conf: configuration. +        :param afi: ip address type. +        :return: generated rule configuration dictionary. +        """ +        cfg = {} +        out = findall(r"[^\s]+", conf, M) +        if out: +            cfg["direction"] = out[0].strip("'") +            if afi == "ipv6": +                out = findall(r"[^\s]+ ipv6-name (?:\'*)(\S+)(?:\'*)", conf, M) +                if out: +                    cfg["name"] = str(out[0]).strip("'") +            else: +                out = findall(r"[^\s]+ name (?:\'*)(\S+)(?:\'*)", conf, M) +                if out: +                    cfg["name"] = out[-1].strip("'") +        return cfg diff --git a/plugins/module_utils/network/vyos/utils/utils.py b/plugins/module_utils/network/vyos/utils/utils.py index 402adfc9..4635234c 100644 --- a/plugins/module_utils/network/vyos/utils/utils.py +++ b/plugins/module_utils/network/vyos/utils/utils.py @@ -14,9 +14,10 @@ from ansible_collections.ansible.netcommon.plugins.module_utils.compat import (  def search_obj_in_list(name, lst, key="name"): -    for item in lst: -        if item[key] == name: -            return item +    if lst: +        for item in lst: +            if item[key] == name: +                return item      return None | 
