1#
2# -*- coding: utf-8 -*-
3# Copyright 2019 Red Hat
4# GNU General Public License v3.0+
5# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6"""
7The vyos l3_interfaces fact class
8It is in this file the configuration is collected from the device
9for a given resource, parsed, and the facts tree is populated
10based on the configuration.
11"""
12
13from __future__ import absolute_import, division, print_function
14
15__metaclass__ = type
16
17
18import re
19from copy import deepcopy
20from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
21    utils,
22)
23from ansible.module_utils.six import iteritems
24from ansible_collections.ansible.netcommon.plugins.module_utils.compat import (
25    ipaddress,
26)
27from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.l3_interfaces.l3_interfaces import (
28    L3_interfacesArgs,
29)
30
31
32class L3_interfacesFacts(object):
33    """ The vyos l3_interfaces fact class
34    """
35
36    def __init__(self, module, subspec="config", options="options"):
37        self._module = module
38        self.argument_spec = L3_interfacesArgs.argument_spec
39        spec = deepcopy(self.argument_spec)
40        if subspec:
41            if options:
42                facts_argument_spec = spec[subspec][options]
43            else:
44                facts_argument_spec = spec[subspec]
45        else:
46            facts_argument_spec = spec
47
48        self.generated_spec = utils.generate_dict(facts_argument_spec)
49
50    def populate_facts(self, connection, ansible_facts, data=None):
51        """ Populate the facts for l3_interfaces
52        :param connection: the device connection
53        :param ansible_facts: Facts dictionary
54        :param data: previously collected conf
55        :rtype: dictionary
56        :returns: facts
57        """
58        if not data:
59            data = connection.get_config()
60
61        # operate on a collection of resource x
62        objs = []
63        interface_names = re.findall(
64            r"set interfaces (?:ethernet|bonding|vti|vxlan) (?:\'*)(\S+)(?:\'*)",
65            data,
66            re.M,
67        )
68        if interface_names:
69            for interface in set(interface_names):
70                intf_regex = r" %s .+$" % interface
71                cfg = re.findall(intf_regex, data, re.M)
72                obj = self.render_config(cfg)
73                obj["name"] = interface.strip("'")
74                if obj:
75                    objs.append(obj)
76
77        ansible_facts["ansible_network_resources"].pop("l3_interfaces", None)
78        facts = {}
79        if objs:
80            facts["l3_interfaces"] = []
81            params = utils.validate_config(
82                self.argument_spec, {"config": objs}
83            )
84            for cfg in params["config"]:
85                facts["l3_interfaces"].append(utils.remove_empties(cfg))
86
87        ansible_facts["ansible_network_resources"].update(facts)
88        return ansible_facts
89
90    def render_config(self, conf):
91        """
92        Render config as dictionary structure and delete keys from spec for null values
93        :param spec: The facts tree, generated from the argspec
94        :param conf: The configuration
95        :rtype: dictionary
96        :returns: The generated config
97        """
98        vif_conf = "\n".join(filter(lambda x: ("vif" in x), conf))
99        eth_conf = "\n".join(filter(lambda x: ("vif" not in x), conf))
100        config = self.parse_attribs(eth_conf)
101        config["vifs"] = self.parse_vifs(vif_conf)
102
103        return utils.remove_empties(config)
104
105    def parse_vifs(self, conf):
106        vif_names = re.findall(r"vif (\d+)", conf, re.M)
107        vifs_list = None
108        if vif_names:
109            vifs_list = []
110            for vif in set(vif_names):
111                vif_regex = r" %s .+$" % vif
112                cfg = "\n".join(re.findall(vif_regex, conf, re.M))
113                obj = self.parse_attribs(cfg)
114                obj["vlan_id"] = vif
115                if obj:
116                    vifs_list.append(obj)
117
118        return vifs_list
119
120    def parse_attribs(self, conf):
121        config = {}
122        ipaddrs = re.findall(r"address (\S+)", conf, re.M)
123        config["ipv4"] = []
124        config["ipv6"] = []
125
126        for item in ipaddrs:
127            item = item.strip("'")
128            if item == "dhcp":
129                config["ipv4"].append({"address": item})
130            elif item == "dhcpv6":
131                config["ipv6"].append({"address": item})
132            else:
133                ip_version = ipaddress.ip_address(item.split("/")[0]).version
134                if ip_version == 4:
135                    config["ipv4"].append({"address": item})
136                else:
137                    config["ipv6"].append({"address": item})
138
139        for key, value in iteritems(config):
140            if value == []:
141                config[key] = None
142
143        return utils.remove_empties(config)
144