summaryrefslogtreecommitdiff
path: root/plugins/module_utils
diff options
context:
space:
mode:
authorBradley A. Thornton <bthornto@thethorntons.net>2019-08-09 07:37:28 -0700
committerBradley A. Thornton <bthornto@thethorntons.net>2019-08-09 07:37:28 -0700
commit8a50be4b9309387624e92a154366b34fa512cd8b (patch)
tree27c001d596821591836fe519b4d97384e434eb1a /plugins/module_utils
parent00ae0cf5ce60cf6149e67f5c83356a2c1877a35f (diff)
downloadvyos.vyos-8a50be4b9309387624e92a154366b34fa512cd8b.tar.gz
vyos.vyos-8a50be4b9309387624e92a154366b34fa512cd8b.zip
add subdirs
Diffstat (limited to 'plugins/module_utils')
-rw-r--r--plugins/module_utils/network/vyos/argspec/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/argspec/facts/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/argspec/facts/facts.py31
-rw-r--r--plugins/module_utils/network/vyos/argspec/interfaces/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py67
-rw-r--r--plugins/module_utils/network/vyos/argspec/l3_interfaces/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/argspec/l3_interfaces/l3_interfaces.py101
-rw-r--r--plugins/module_utils/network/vyos/config/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/config/interfaces/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/config/interfaces/interfaces.py283
-rw-r--r--plugins/module_utils/network/vyos/config/l3_interfaces/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/config/l3_interfaces/l3_interfaces.py277
-rw-r--r--plugins/module_utils/network/vyos/facts/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/facts/facts.py69
-rw-r--r--plugins/module_utils/network/vyos/facts/interfaces/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/facts/interfaces/interfaces.py125
-rw-r--r--plugins/module_utils/network/vyos/facts/l3_interfaces/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py132
-rw-r--r--plugins/module_utils/network/vyos/facts/legacy/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/facts/legacy/base.py161
-rw-r--r--plugins/module_utils/network/vyos/utils/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/utils/utils.py68
22 files changed, 1314 insertions, 0 deletions
diff --git a/plugins/module_utils/network/vyos/argspec/__init__.py b/plugins/module_utils/network/vyos/argspec/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/__init__.py
diff --git a/plugins/module_utils/network/vyos/argspec/facts/__init__.py b/plugins/module_utils/network/vyos/argspec/facts/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/facts/__init__.py
diff --git a/plugins/module_utils/network/vyos/argspec/facts/facts.py b/plugins/module_utils/network/vyos/argspec/facts/facts.py
new file mode 100644
index 0000000..456c8bd
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/facts/facts.py
@@ -0,0 +1,31 @@
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The arg spec for the vyos facts module.
+"""
+
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class FactsArgs(object): # pylint: disable=R0903
+ """ The arg spec for the vyos facts module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ choices = [
+ 'all',
+ 'interfaces',
+ '!interfaces',
+ 'l3_interfaces',
+ '!l3_interfaces'
+ ]
+
+ argument_spec = {
+ 'gather_subset': dict(default=['!config'], type='list'),
+ 'gather_network_resources': dict(choices=choices, type='list'),
+ }
diff --git a/plugins/module_utils/network/vyos/argspec/interfaces/__init__.py b/plugins/module_utils/network/vyos/argspec/interfaces/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/interfaces/__init__.py
diff --git a/plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py b/plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py
new file mode 100644
index 0000000..d6ab446
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py
@@ -0,0 +1,67 @@
+# 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_interfaces module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class InterfacesArgs(object): # pylint: disable=R0903
+ """The arg spec for the vyos_interfaces module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = \
+ {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'description': {'type': 'str'},
+ 'duplex': {'choices': ['full', 'half', 'auto']},
+ 'enabled': {'default': True, 'type': 'bool'},
+ 'mtu': {'type': 'int'},
+ 'name': {'required': True, 'type': 'str'},
+ 'speed': {'choices': ['auto', '10', '100', '1000', '2500',
+ '10000'],
+ 'type': 'str'},
+ 'vifs': {
+ 'elements': 'dict',
+ 'options': {
+ 'vlan_id': {'type': 'int'},
+ 'description': {'type': 'str'},
+ 'enabled': {'default': True, 'type': 'bool'},
+ 'mtu': {'type': 'int'}
+ },
+ 'type': 'list'
+ },
+ },
+ 'type': 'list'
+ },
+ 'state': {'choices': ['merged', 'replaced',
+ 'overridden', 'deleted'],
+ 'default': 'merged',
+ 'type': 'str'}
+ } # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/l3_interfaces/__init__.py b/plugins/module_utils/network/vyos/argspec/l3_interfaces/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/l3_interfaces/__init__.py
diff --git a/plugins/module_utils/network/vyos/argspec/l3_interfaces/l3_interfaces.py b/plugins/module_utils/network/vyos/argspec/l3_interfaces/l3_interfaces.py
new file mode 100644
index 0000000..e5785a8
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/l3_interfaces/l3_interfaces.py
@@ -0,0 +1,101 @@
+#
+# -*- 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_l3_interfaces module
+"""
+
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class L3_interfacesArgs(object): # pylint: disable=R0903
+ """The arg spec for the vyos_l3_interfaces module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'ipv4': {
+ 'elements': 'dict',
+ 'options': {
+ 'address': {
+ 'type': 'str'
+ }
+ },
+ 'type': 'list'
+ },
+ 'ipv6': {
+ 'elements': 'dict',
+ 'options': {
+ 'address': {
+ 'type': 'str'
+ }
+ },
+ 'type': 'list'
+ },
+ 'name': {
+ 'required': True,
+ 'type': 'str'
+ },
+ 'vifs': {
+ 'elements': 'dict',
+ 'options': {
+ 'ipv4': {
+ 'elements': 'dict',
+ 'options': {
+ 'address': {
+ 'type': 'str'
+ }
+ },
+ 'type': 'list'
+ },
+ 'ipv6': {
+ 'elements': 'dict',
+ 'options': {
+ 'address': {
+ 'type': 'str'
+ }
+ },
+ 'type': 'list'
+ },
+ 'vlan_id': {
+ 'type': 'int'
+ }
+ },
+ 'type': 'list'
+ }
+ },
+ 'type': 'list'
+ },
+ 'state': {
+ 'choices': ['merged', 'replaced', 'overridden', 'deleted'],
+ 'default': 'merged',
+ 'type': 'str'
+ }
+ } # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/config/__init__.py b/plugins/module_utils/network/vyos/config/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/module_utils/network/vyos/config/__init__.py
diff --git a/plugins/module_utils/network/vyos/config/interfaces/__init__.py b/plugins/module_utils/network/vyos/config/interfaces/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/module_utils/network/vyos/config/interfaces/__init__.py
diff --git a/plugins/module_utils/network/vyos/config/interfaces/interfaces.py b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py
new file mode 100644
index 0000000..b17971c
--- /dev/null
+++ b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py
@@ -0,0 +1,283 @@
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The vyos_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.module_utils.network.common.cfg.base import ConfigBase
+from ansible.module_utils.network.common.utils import to_list, dict_diff, remove_empties
+from ansible.module_utils.six import iteritems
+from ansible_collections.vyos.vyos.plugins.module_utils.network. \
+ vyos.facts.facts import Facts
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network. \
+ vyos.utils.utils import search_obj_in_list, get_interface_type, dict_delete
+
+
+
+class Interfaces(ConfigBase):
+ """
+ The vyos_interfaces class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'interfaces'
+ ]
+
+ def __init__(self, module):
+ super(Interfaces, self).__init__(module)
+
+ def get_interfaces_facts(self):
+ """ 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)
+ interfaces_facts = facts['ansible_network_resources'].get('interfaces')
+ if not interfaces_facts:
+ return []
+ return interfaces_facts
+
+ def execute_module(self):
+ """ Execute the module
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ commands = list()
+ warnings = list()
+
+ existing_interfaces_facts = self.get_interfaces_facts()
+ commands.extend(self.set_config(existing_interfaces_facts))
+ if commands:
+ if self._module.check_mode:
+ resp = self._connection.edit_config(commands, commit=False)
+ else:
+ resp = self._connection.edit_config(commands)
+ result['changed'] = True
+
+ result['commands'] = commands
+
+ if self._module._diff:
+ result['diff'] = resp['diff'] if result['changed'] else None
+
+ changed_interfaces_facts = self.get_interfaces_facts()
+
+ result['before'] = existing_interfaces_facts
+ if result['changed']:
+ result['after'] = changed_interfaces_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_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_interfaces_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """ 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 = []
+ state = self._module.params['state']
+ if state == 'overridden':
+ commands.extend(self._state_overridden(want=want, have=have))
+
+ elif state == 'deleted':
+ if not want:
+ for intf in have:
+ commands.extend(
+ self._state_deleted(
+ {'name': intf['name']},
+ intf
+ )
+ )
+ else:
+ for item in want:
+ obj_in_have = search_obj_in_list(item['name'], have)
+ commands.extend(
+ self._state_deleted(
+ item, obj_in_have
+ )
+ )
+ else:
+ for item in want:
+ name = item['name']
+ obj_in_have = search_obj_in_list(name, have)
+
+ if not obj_in_have:
+ obj_in_have = {'name': item['name']}
+
+ elif state == 'merged':
+ commands.extend(
+ self._state_merged(
+ item, obj_in_have
+ )
+ )
+
+ elif state == 'replaced':
+ commands.extend(
+ self._state_replaced(
+ item, obj_in_have
+ )
+ )
+
+ 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:
+ commands.extend(self._state_deleted(want, have))
+
+ 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 = []
+
+ for intf in have:
+ intf_in_want = search_obj_in_list(intf['name'], want)
+ if not intf_in_want:
+ commands.extend(self._state_deleted({'name': intf['name']}, intf))
+
+ for intf in want:
+ intf_in_have = search_obj_in_list(intf['name'], have)
+ commands.extend(self._state_replaced(intf, intf_in_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 = []
+ want_copy = deepcopy(remove_empties(want))
+ have_copy = deepcopy(have)
+
+ want_vifs = want_copy.pop('vifs', [])
+ have_vifs = have_copy.pop('vifs', [])
+
+ updates = dict_diff(have_copy, want_copy)
+
+ if updates:
+ for key, value in iteritems(updates):
+ commands.append(self._compute_commands(key=key, value=value, interface=want_copy['name']))
+
+ if want_vifs:
+ for want_vif in want_vifs:
+ have_vif = search_obj_in_list(want_vif['vlan_id'], have_vifs, key='vlan_id')
+ if not have_vif:
+ have_vif = {'vlan_id': want_vif['vlan_id'], 'enabled': True}
+
+ vif_updates = dict_diff(have_vif, want_vif)
+ if vif_updates:
+ for key, value in iteritems(vif_updates):
+ commands.append(self._compute_commands(key=key, value=value, interface=want_copy['name'], vif=want_vif['vlan_id']))
+
+ 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 = []
+
+ want_copy = deepcopy(remove_empties(want))
+ have_copy = deepcopy(have)
+
+ want_vifs = want_copy.pop('vifs', [])
+ have_vifs = have_copy.pop('vifs', [])
+
+ for key in dict_delete(have_copy, want_copy).keys():
+ if key == 'enabled':
+ continue
+ commands.append(self._compute_commands(key=key, interface=want_copy['name'], remove=True))
+ if have_copy['enabled'] is False:
+ commands.append(self._compute_commands(key='enabled', value=True, interface=want_copy['name']))
+
+ if have_vifs:
+ for have_vif in have_vifs:
+ want_vif = search_obj_in_list(have_vif['vlan_id'], want_vifs, key='vlan_id')
+ if not want_vif:
+ want_vif = {'vlan_id': have_vif['vlan_id'], 'enabled': True}
+
+ for key in dict_delete(have_vif, want_vif).keys():
+ if key == 'enabled':
+ continue
+ commands.append(self._compute_commands(key=key, interface=want_copy['name'], vif=want_vif['vlan_id'], remove=True))
+ if have_vif['enabled'] is False:
+ commands.append(self._compute_commands(key='enabled', value=True, interface=want_copy['name'], vif=want_vif['vlan_id']))
+
+ return commands
+
+ def _compute_commands(self, interface, key, vif=None, value=None, remove=False):
+ intf_context = 'interfaces {0} {1}'.format(get_interface_type(interface), interface)
+ set_cmd = 'set {0}'.format(intf_context)
+ del_cmd = 'delete {0}'.format(intf_context)
+
+ if vif:
+ set_cmd = set_cmd + (' vif {0}'.format(vif))
+ del_cmd = del_cmd + (' vif {0}'.format(vif))
+
+ if key == 'enabled':
+ if not value:
+ command = "{0} disable".format(set_cmd)
+ else:
+ command = "{0} disable".format(del_cmd)
+ else:
+ if not remove:
+ command = "{0} {1} '{2}'".format(set_cmd, key, value)
+ else:
+ command = "{0} {1}".format(del_cmd, key)
+
+ return command
diff --git a/plugins/module_utils/network/vyos/config/l3_interfaces/__init__.py b/plugins/module_utils/network/vyos/config/l3_interfaces/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/module_utils/network/vyos/config/l3_interfaces/__init__.py
diff --git a/plugins/module_utils/network/vyos/config/l3_interfaces/l3_interfaces.py b/plugins/module_utils/network/vyos/config/l3_interfaces/l3_interfaces.py
new file mode 100644
index 0000000..9027c84
--- /dev/null
+++ b/plugins/module_utils/network/vyos/config/l3_interfaces/l3_interfaces.py
@@ -0,0 +1,277 @@
+#
+# -*- 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_l3_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.module_utils.network.common.cfg.base import ConfigBase
+from ansible.module_utils.network.common.utils import to_list, remove_empties
+from ansible.module_utils.six import iteritems
+from ansible_collections.vyos.vyos.plugins.module_utils.network. \
+ vyos.facts.facts import Facts
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network. \
+ vyos.utils.utils import search_obj_in_list, get_interface_type, diff_list_of_dicts
+
+
+
+class L3_interfaces(ConfigBase):
+ """
+ The vyos_l3_interfaces class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'l3_interfaces',
+ ]
+
+ def __init__(self, module):
+ super(L3_interfaces, self).__init__(module)
+
+ def get_l3_interfaces_facts(self):
+ """ 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)
+ l3_interfaces_facts = facts['ansible_network_resources'].get('l3_interfaces')
+ if not l3_interfaces_facts:
+ return []
+ return l3_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()
+
+ existing_l3_interfaces_facts = self.get_l3_interfaces_facts()
+ commands.extend(self.set_config(existing_l3_interfaces_facts))
+ if commands:
+ if self._module.check_mode:
+ resp = self._connection.edit_config(commands, commit=False)
+ else:
+ resp = self._connection.edit_config(commands)
+ result['changed'] = True
+
+ result['commands'] = commands
+
+ if self._module._diff:
+ result['diff'] = resp['diff'] if result['changed'] else None
+
+ changed_l3_interfaces_facts = self.get_l3_interfaces_facts()
+
+ result['before'] = existing_l3_interfaces_facts
+ if result['changed']:
+ result['after'] = changed_l3_interfaces_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_l3_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_l3_interfaces_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """ 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 = []
+ state = self._module.params['state']
+ if state == 'overridden':
+ commands.extend(self._state_overridden(want=want, have=have))
+
+ elif state == 'deleted':
+ if not want:
+ for intf in have:
+ commands.extend(
+ self._state_deleted(
+ {'name': intf['name']},
+ intf
+ )
+ )
+ else:
+ for item in want:
+ obj_in_have = search_obj_in_list(item['name'], have)
+ commands.extend(
+ self._state_deleted(
+ item, obj_in_have
+ )
+ )
+ else:
+ for item in want:
+ name = item['name']
+ obj_in_have = search_obj_in_list(name, have)
+
+ if not obj_in_have:
+ obj_in_have = {'name': item['name']}
+
+ if state == 'merged':
+ commands.extend(
+ self._state_merged(
+ item, obj_in_have
+ )
+ )
+
+ elif state == 'replaced':
+ commands.extend(
+ self._state_replaced(
+ item, obj_in_have
+ )
+ )
+
+ 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:
+ commands.extend(self._state_deleted(want, have))
+
+ 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 = []
+
+ for intf in have:
+ intf_in_want = search_obj_in_list(intf['name'], want)
+ if not intf_in_want:
+ commands.extend(self._state_deleted({'name': intf['name']}, intf))
+
+ for intf in want:
+ intf_in_have = search_obj_in_list(intf['name'], have)
+ commands.extend(self._state_replaced(intf, intf_in_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 = []
+ want_copy = deepcopy(remove_empties(want))
+ have_copy = deepcopy(remove_empties(have))
+
+ want_vifs = want_copy.pop('vifs', [])
+ have_vifs = have_copy.pop('vifs', [])
+
+ for update in self._get_updates(want_copy, have_copy):
+ for key, value in iteritems(update):
+ commands.append(self._compute_commands(key=key, value=value, interface=want_copy['name']))
+
+ if want_vifs:
+ for want_vif in want_vifs:
+ have_vif = search_obj_in_list(want_vif['vlan_id'], have_vifs, key='vlan_id')
+ if not have_vif:
+ have_vif = {}
+
+ for update in self._get_updates(want_vif, have_vif):
+ for key, value in iteritems(update):
+ commands.append(self._compute_commands(key=key, value=value, interface=want_copy['name'], vif=want_vif['vlan_id']))
+
+ 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 = []
+ want_copy = deepcopy(remove_empties(want))
+ have_copy = deepcopy(have)
+
+ want_vifs = want_copy.pop('vifs', [])
+ have_vifs = have_copy.pop('vifs', [])
+
+ for update in self._get_updates(have_copy, want_copy):
+ for key, value in iteritems(update):
+ commands.append(self._compute_commands(key=key, value=value, interface=want_copy['name'], remove=True))
+
+ if have_vifs:
+ for have_vif in have_vifs:
+ want_vif = search_obj_in_list(have_vif['vlan_id'], want_vifs, key='vlan_id')
+ if not want_vif:
+ want_vif = {'vlan_id': have_vif['vlan_id']}
+
+ for update in self._get_updates(have_vif, want_vif):
+ for key, value in iteritems(update):
+ commands.append(self._compute_commands(key=key, interface=want_copy['name'], value=value, vif=want_vif['vlan_id'], remove=True))
+
+ return commands
+
+ def _compute_commands(self, interface, key, vif=None, value=None, remove=False):
+ intf_context = 'interfaces {0} {1}'.format(get_interface_type(interface), interface)
+ set_cmd = 'set {0}'.format(intf_context)
+ del_cmd = 'delete {0}'.format(intf_context)
+
+ if vif:
+ set_cmd = set_cmd + (' vif {0}'.format(vif))
+ del_cmd = del_cmd + (' vif {0}'.format(vif))
+
+ if remove:
+ command = "{0} {1} '{2}'".format(del_cmd, key, value)
+ else:
+ command = "{0} {1} '{2}'".format(set_cmd, key, value)
+
+ return command
+
+ def _get_updates(self, want, have):
+ updates = []
+
+ updates = diff_list_of_dicts(want.get('ipv4', []), have.get('ipv4', []))
+ updates.extend(diff_list_of_dicts(want.get('ipv6', []), have.get('ipv6', [])))
+
+ return updates
diff --git a/plugins/module_utils/network/vyos/facts/__init__.py b/plugins/module_utils/network/vyos/facts/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/__init__.py
diff --git a/plugins/module_utils/network/vyos/facts/facts.py b/plugins/module_utils/network/vyos/facts/facts.py
new file mode 100644
index 0000000..a065eaf
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/facts.py
@@ -0,0 +1,69 @@
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The facts class for vyos
+this file validates each subset of facts and selectively
+calls the appropriate facts gathering function
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network. \
+ vyos.argspec.facts.facts import FactsArgs
+
+from ansible.module_utils.network.common.facts.facts import FactsBase
+from ansible_collections.vyos.vyos.plugins.module_utils.network. \
+ vyos.facts.interfaces.interfaces import InterfacesFacts
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network. \
+ vyos.facts.l3_interfaces.l3_interfaces import L3_interfacesFacts
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network. \
+ vyos.facts.legacy.base import Default, Neighbors, Config
+
+from ansible.module_utils. \
+ network.vyos.vyos import run_commands, get_capabilities
+
+
+FACT_LEGACY_SUBSETS = dict(
+ default=Default,
+ neighbors=Neighbors,
+ config=Config
+)
+FACT_RESOURCE_SUBSETS = dict(
+ interfaces=InterfacesFacts,
+ l3_interfaces=L3_interfacesFacts
+)
+
+
+class Facts(FactsBase):
+ """ The fact class for vyos
+ """
+
+ VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys())
+ VALID_RESOURCE_SUBSETS = frozenset(FACT_RESOURCE_SUBSETS.keys())
+
+ def __init__(self, module):
+ super(Facts, self).__init__(module)
+
+ def get_facts(self, legacy_facts_type=None, resource_facts_type=None, data=None):
+ """ Collect the facts for vyos
+
+ :param legacy_facts_type: List of legacy facts types
+ :param resource_facts_type: List of resource fact types
+ :param data: previously collected conf
+ :rtype: dict
+ :return: the facts gathered
+ """
+ netres_choices = FactsArgs.argument_spec['gather_network_resources'].get('choices', [])
+ if self.VALID_RESOURCE_SUBSETS:
+ self.get_network_resources_facts(netres_choices, FACT_RESOURCE_SUBSETS,
+ resource_facts_type, data)
+
+ if self.VALID_LEGACY_GATHER_SUBSETS:
+ self.get_network_legacy_facts(FACT_LEGACY_SUBSETS, legacy_facts_type)
+
+ return self.ansible_facts, self._warnings
diff --git a/plugins/module_utils/network/vyos/facts/interfaces/__init__.py b/plugins/module_utils/network/vyos/facts/interfaces/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/interfaces/__init__.py
diff --git a/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py b/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py
new file mode 100644
index 0000000..cc89e4f
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py
@@ -0,0 +1,125 @@
+#
+# -*- 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 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, M
+from copy import deepcopy
+from ansible.module_utils.network.common import utils
+from ansible_collections.vyos.vyos.plugins.module_utils.network. \
+ vyos.argspec.interfaces.interfaces import InterfacesArgs
+
+
+
+class InterfacesFacts(object):
+ """ The vyos interfaces fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = 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 populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for interfaces
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if not data:
+ data = connection.get_config(flags=['| grep interfaces'])
+
+ objs = []
+ interface_names = findall(r'^set interfaces (?:ethernet|bonding|vti|loopback|vxlan) (?:\'*)(\S+)(?:\'*)',
+ data, M)
+ if interface_names:
+ for interface in set(interface_names):
+ intf_regex = r' %s .+$' % interface.strip("'")
+ cfg = findall(intf_regex, data, M)
+ obj = self.render_config(cfg)
+ obj['name'] = interface.strip("'")
+ if obj:
+ objs.append(obj)
+ facts = {}
+ if objs:
+ facts['interfaces'] = []
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ for cfg in params['config']:
+ facts['interfaces'].append(utils.remove_empties(cfg))
+
+ ansible_facts['ansible_network_resources'].update(facts)
+ return ansible_facts
+
+ 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
+ """
+ vif_conf = '\n'.join(filter(lambda x: ('vif' in x), conf))
+ eth_conf = '\n'.join(filter(lambda x: ('vif' not in x), conf))
+ config = self.parse_attribs(
+ ['description', 'speed', 'mtu', 'duplex'], eth_conf)
+ config['vifs'] = self.parse_vifs(vif_conf)
+
+ return utils.remove_empties(config)
+
+ def parse_vifs(self, conf):
+ vif_names = findall(r'vif (?:\'*)(\d+)(?:\'*)', conf, M)
+ vifs_list = None
+
+ if vif_names:
+ vifs_list = []
+ for vif in set(vif_names):
+ vif_regex = r' %s .+$' % vif
+ cfg = '\n'.join(findall(vif_regex, conf, M))
+ obj = self.parse_attribs(['description', 'mtu'], cfg)
+ obj['vlan_id'] = int(vif)
+ if obj:
+ vifs_list.append(obj)
+ vifs_list = sorted(vifs_list, key=lambda i: i['vlan_id'])
+
+ return vifs_list
+
+ def parse_attribs(self, attribs, conf):
+ config = {}
+ for item in attribs:
+ value = utils.parse_conf_arg(conf, item)
+ if value and item == 'mtu':
+ config[item] = int(value.strip("'"))
+ elif value:
+ config[item] = value.strip("'")
+ else:
+ config[item] = None
+ if 'disable' in conf:
+ config['enabled'] = False
+ else:
+ config['enabled'] = True
+
+ return utils.remove_empties(config)
diff --git a/plugins/module_utils/network/vyos/facts/l3_interfaces/__init__.py b/plugins/module_utils/network/vyos/facts/l3_interfaces/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/l3_interfaces/__init__.py
diff --git a/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py b/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py
new file mode 100644
index 0000000..61f635b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py
@@ -0,0 +1,132 @@
+#
+# -*- 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 l3_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
+
+
+import re
+from copy import deepcopy
+from ansible.module_utils.network.common import utils
+from ansible.module_utils.six import iteritems
+from ansible.module_utils.compat import ipaddress
+from ansible_collections.vyos.vyos.plugins.module_utils.network. \
+ vyos.argspec.l3_interfaces.l3_interfaces import L3_interfacesArgs
+
+
+
+class L3_interfacesFacts(object):
+ """ The vyos l3_interfaces fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = L3_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 populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for l3_interfaces
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if not data:
+ data = connection.get_config()
+
+ # operate on a collection of resource x
+ objs = []
+ interface_names = re.findall(r'set interfaces (?:ethernet|bonding|vti|vxlan) (?:\'*)(\S+)(?:\'*)', data, re.M)
+ if interface_names:
+ for interface in set(interface_names):
+ intf_regex = r' %s .+$' % interface
+ cfg = re.findall(intf_regex, data, re.M)
+ obj = self.render_config(cfg)
+ obj['name'] = interface.strip("'")
+ if obj:
+ objs.append(obj)
+
+ ansible_facts['ansible_network_resources'].pop('l3_interfaces', None)
+ facts = {}
+ if objs:
+ facts['l3_interfaces'] = []
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ for cfg in params['config']:
+ facts['l3_interfaces'].append(utils.remove_empties(cfg))
+
+ ansible_facts['ansible_network_resources'].update(facts)
+ return ansible_facts
+
+ 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
+ """
+ vif_conf = '\n'.join(filter(lambda x: ('vif' in x), conf))
+ eth_conf = '\n'.join(filter(lambda x: ('vif' not in x), conf))
+ config = self.parse_attribs(eth_conf)
+ config['vifs'] = self.parse_vifs(vif_conf)
+
+ return utils.remove_empties(config)
+
+ def parse_vifs(self, conf):
+ vif_names = re.findall(r'vif (\d+)', conf, re.M)
+ vifs_list = None
+ if vif_names:
+ vifs_list = []
+ for vif in set(vif_names):
+ vif_regex = r' %s .+$' % vif
+ cfg = '\n'.join(re.findall(vif_regex, conf, re.M))
+ obj = self.parse_attribs(cfg)
+ obj['vlan_id'] = vif
+ if obj:
+ vifs_list.append(obj)
+
+ return vifs_list
+
+ def parse_attribs(self, conf):
+ config = {}
+ ipaddrs = re.findall(r'address (\S+)', conf, re.M)
+ config['ipv4'] = []
+ config['ipv6'] = []
+
+ for item in ipaddrs:
+ item = item.strip("'")
+ if item == 'dhcp':
+ config['ipv4'].append({'address': item})
+ elif item == 'dhcpv6':
+ config['ipv6'].append({'address': item})
+ else:
+ ip_version = ipaddress.ip_address(item.split("/")[0]).version
+ if ip_version == 4:
+ config['ipv4'].append({'address': item})
+ else:
+ config['ipv6'].append({'address': item})
+
+ for key, value in iteritems(config):
+ if value == []:
+ config[key] = None
+
+ return utils.remove_empties(config)
diff --git a/plugins/module_utils/network/vyos/facts/legacy/__init__.py b/plugins/module_utils/network/vyos/facts/legacy/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/legacy/__init__.py
diff --git a/plugins/module_utils/network/vyos/facts/legacy/base.py b/plugins/module_utils/network/vyos/facts/legacy/base.py
new file mode 100644
index 0000000..cdf0cce
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/legacy/base.py
@@ -0,0 +1,161 @@
+# -*- 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 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
+
+
+import platform
+import re
+from ansible.module_utils. \
+ network.vyos.vyos import run_commands, get_capabilities
+
+
+class LegacyFactsBase(object):
+
+ COMMANDS = frozenset()
+
+ def __init__(self, module):
+ self.module = module
+ self.facts = dict()
+ self.warnings = list()
+ self.responses = None
+
+ def populate(self):
+ self.responses = run_commands(self.module, list(self.COMMANDS))
+
+
+class Default(LegacyFactsBase):
+
+ COMMANDS = [
+ 'show version',
+ ]
+
+ def populate(self):
+ super(Default, self).populate()
+ data = self.responses[0]
+ self.facts['serialnum'] = self.parse_serialnum(data)
+ self.facts.update(self.platform_facts())
+
+ def parse_serialnum(self, data):
+ match = re.search(r'HW S/N:\s+(\S+)', data)
+ if match:
+ return match.group(1)
+
+ def platform_facts(self):
+ platform_facts = {}
+
+ resp = get_capabilities(self.module)
+ device_info = resp['device_info']
+
+ platform_facts['system'] = device_info['network_os']
+
+ for item in ('model', 'image', 'version', 'platform', 'hostname'):
+ val = device_info.get('network_os_%s' % item)
+ if val:
+ platform_facts[item] = val
+
+ platform_facts['api'] = resp['network_api']
+ platform_facts['python_version'] = platform.python_version()
+
+ return platform_facts
+
+
+class Config(LegacyFactsBase):
+
+ COMMANDS = [
+ 'show configuration commands',
+ 'show system commit',
+ ]
+
+ def populate(self):
+ super(Config, self).populate()
+
+ self.facts['config'] = self.responses
+
+ commits = self.responses[1]
+ entries = list()
+ entry = None
+
+ for line in commits.split('\n'):
+ match = re.match(r'(\d+)\s+(.+)by(.+)via(.+)', line)
+ if match:
+ if entry:
+ entries.append(entry)
+
+ entry = dict(revision=match.group(1),
+ datetime=match.group(2),
+ by=str(match.group(3)).strip(),
+ via=str(match.group(4)).strip(),
+ comment=None)
+ else:
+ entry['comment'] = line.strip()
+
+ self.facts['commits'] = entries
+
+
+class Neighbors(LegacyFactsBase):
+
+ COMMANDS = [
+ 'show lldp neighbors',
+ 'show lldp neighbors detail',
+ ]
+
+ def populate(self):
+ super(Neighbors, self).populate()
+
+ all_neighbors = self.responses[0]
+ if 'LLDP not configured' not in all_neighbors:
+ neighbors = self.parse(
+ self.responses[1]
+ )
+ self.facts['neighbors'] = self.parse_neighbors(neighbors)
+
+ def parse(self, data):
+ parsed = list()
+ values = None
+ for line in data.split('\n'):
+ if not line:
+ continue
+ elif line[0] == ' ':
+ values += '\n%s' % line
+ elif line.startswith('Interface'):
+ if values:
+ parsed.append(values)
+ values = line
+ if values:
+ parsed.append(values)
+ return parsed
+
+ def parse_neighbors(self, data):
+ facts = dict()
+ for item in data:
+ interface = self.parse_interface(item)
+ host = self.parse_host(item)
+ port = self.parse_port(item)
+ if interface not in facts:
+ facts[interface] = list()
+ facts[interface].append(dict(host=host, port=port))
+ return facts
+
+ def parse_interface(self, data):
+ match = re.search(r'^Interface:\s+(\S+),', data)
+ return match.group(1)
+
+ def parse_host(self, data):
+ match = re.search(r'SysName:\s+(.+)$', data, re.M)
+ if match:
+ return match.group(1)
+
+ def parse_port(self, data):
+ match = re.search(r'PortDescr:\s+(.+)$', data, re.M)
+ if match:
+ return match.group(1)
diff --git a/plugins/module_utils/network/vyos/utils/__init__.py b/plugins/module_utils/network/vyos/utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/module_utils/network/vyos/utils/__init__.py
diff --git a/plugins/module_utils/network/vyos/utils/utils.py b/plugins/module_utils/network/vyos/utils/utils.py
new file mode 100644
index 0000000..f59c24f
--- /dev/null
+++ b/plugins/module_utils/network/vyos/utils/utils.py
@@ -0,0 +1,68 @@
+# -*- 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)
+
+# utils
+
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+def search_obj_in_list(name, lst, key='name'):
+ for item in lst:
+ if item[key] == name:
+ return item
+ return None
+
+
+def get_interface_type(interface):
+ """Gets the type of interface
+ """
+ if interface.startswith('eth'):
+ return 'ethernet'
+ elif interface.startswith('bond'):
+ return 'bonding'
+ elif interface.startswith('vti'):
+ return 'vti'
+ elif interface.startswith('lo'):
+ return 'loopback'
+
+
+def dict_delete(base, comparable):
+ """
+ This function generates a dict containing key, value pairs for keys
+ that are present in the `base` dict but not present in the `comparable`
+ dict.
+
+ :param base: dict object to base the diff on
+ :param comparable: dict object to compare against base
+ :returns: new dict object with key, value pairs that needs to be deleted.
+
+ """
+ to_delete = dict()
+
+ for key in base:
+ if isinstance(base[key], dict):
+ sub_diff = dict_delete(base[key], comparable.get(key, {}))
+ if sub_diff:
+ to_delete[key] = sub_diff
+ else:
+ if key not in comparable:
+ to_delete[key] = base[key]
+
+ return to_delete
+
+
+def diff_list_of_dicts(want, have):
+ diff = []
+
+ set_w = set(tuple(d.items()) for d in want)
+ set_h = set(tuple(d.items()) for d in have)
+ difference = set_w.difference(set_h)
+
+ for element in difference:
+ diff.append(dict((x, y) for x, y in element))
+
+ return diff