diff --git a/changelogs/fragments/80-reformat-files.yaml b/changelogs/fragments/80-reformat-files.yaml new file mode 100644 index 0000000..21719cc --- /dev/null +++ b/changelogs/fragments/80-reformat-files.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - Reformatted files with latest version of Black (20.8b1). diff --git a/plugins/module_utils/network/vyos/argspec/facts/facts.py b/plugins/module_utils/network/vyos/argspec/facts/facts.py index e52b6eb..d78dd3a 100644 --- a/plugins/module_utils/network/vyos/argspec/facts/facts.py +++ b/plugins/module_utils/network/vyos/argspec/facts/facts.py @@ -1,24 +1,23 @@ # 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 - """ + """The arg spec for the vyos facts module""" def __init__(self, **kwargs): pass argument_spec = { "gather_subset": dict( default=["!config"], type="list", elements="str" ), "gather_network_resources": dict(type="list", elements="str"), } diff --git a/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py b/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py index 4c26773..92a0255 100644 --- a/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py +++ b/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py @@ -1,152 +1,151 @@ # # -*- 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_global module """ from __future__ import absolute_import, division, print_function __metaclass__ = type class Firewall_globalArgs(object): # pylint: disable=R0903 - """The arg spec for the vyos_firewall_global module - """ + """The arg spec for the vyos_firewall_global module""" def __init__(self, **kwargs): pass argument_spec = { "config": { "options": { "config_trap": {"type": "bool"}, "group": { "options": { "address_group": { "elements": "dict", "options": { "description": {"type": "str"}, "members": { "elements": "dict", "options": {"address": {"type": "str"}}, "type": "list", }, "name": {"required": True, "type": "str"}, }, "type": "list", }, "network_group": { "elements": "dict", "options": { "description": {"type": "str"}, "members": { "elements": "dict", "options": {"address": {"type": "str"}}, "type": "list", }, "name": {"required": True, "type": "str"}, }, "type": "list", }, "port_group": { "elements": "dict", "options": { "description": {"type": "str"}, "members": { "elements": "dict", "options": {"port": {"type": "str"}}, "type": "list", }, "name": {"required": True, "type": "str"}, }, "type": "list", }, }, "type": "dict", }, "log_martians": {"type": "bool"}, "ping": { "options": { "all": {"type": "bool"}, "broadcast": {"type": "bool"}, }, "type": "dict", }, "route_redirects": { "elements": "dict", "options": { "afi": { "choices": ["ipv4", "ipv6"], "required": True, "type": "str", }, "icmp_redirects": { "options": { "receive": {"type": "bool"}, "send": {"type": "bool"}, }, "type": "dict", }, "ip_src_route": {"type": "bool"}, }, "type": "list", }, "state_policy": { "elements": "dict", "options": { "action": { "choices": ["accept", "drop", "reject"], "type": "str", }, "connection_type": { "choices": ["established", "invalid", "related"], "type": "str", }, "log": {"type": "bool"}, }, "type": "list", }, "syn_cookies": {"type": "bool"}, "twa_hazards_protection": {"type": "bool"}, "validation": { "choices": ["strict", "loose", "disable"], "type": "str", }, }, "type": "dict", }, "running_config": {"type": "str"}, "state": { "choices": [ "merged", "replaced", "deleted", "gathered", "rendered", "parsed", ], "default": "merged", "type": "str", }, } # pylint: disable=C0301 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 index f083485..16e6615 100644 --- a/plugins/module_utils/network/vyos/argspec/firewall_interfaces/firewall_interfaces.py +++ b/plugins/module_utils/network/vyos/argspec/firewall_interfaces/firewall_interfaces.py @@ -1,85 +1,84 @@ # # -*- 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 - """ + """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/argspec/firewall_rules/firewall_rules.py b/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py index a018cc0..2df7758 100644 --- a/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py +++ b/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py @@ -1,263 +1,262 @@ # # -*- 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_rules module """ from __future__ import absolute_import, division, print_function __metaclass__ = type class Firewall_rulesArgs(object): # pylint: disable=R0903 - """The arg spec for the vyos_firewall_rules module - """ + """The arg spec for the vyos_firewall_rules module""" def __init__(self, **kwargs): pass argument_spec = { "config": { "elements": "dict", "options": { "afi": { "choices": ["ipv4", "ipv6"], "required": True, "type": "str", }, "rule_sets": { "elements": "dict", "options": { "default_action": { "choices": ["drop", "reject", "accept"], "type": "str", }, "description": {"type": "str"}, "enable_default_log": {"type": "bool"}, "name": {"type": "str"}, "rules": { "elements": "dict", "options": { "action": { "choices": [ "drop", "reject", "accept", "inspect", ], "type": "str", }, "description": {"type": "str"}, "destination": { "options": { "address": {"type": "str"}, "group": { "options": { "address_group": { "type": "str" }, "network_group": { "type": "str" }, "port_group": {"type": "str"}, }, "type": "dict", }, "port": {"type": "str"}, }, "type": "dict", }, "disabled": {"type": "bool"}, "fragment": { "choices": [ "match-frag", "match-non-frag", ], "type": "str", }, "icmp": { "options": { "code": {"type": "int"}, "type": {"type": "int"}, "type_name": { "choices": [ "any", "echo-reply", "destination-unreachable", "network-unreachable", "host-unreachable", "protocol-unreachable", "port-unreachable", "fragmentation-needed", "source-route-failed", "network-unknown", "host-unknown", "network-prohibited", "host-prohibited", "TOS-network-unreachable", "TOS-host-unreachable", "communication-prohibited", "host-precedence-violation", "precedence-cutoff", "source-quench", "redirect", "network-redirect", "host-redirect", "TOS-network-redirect", "TOS-host-redirect", "echo-request", "router-advertisement", "router-solicitation", "time-exceeded", "ttl-zero-during-transit", "ttl-zero-during-reassembly", "parameter-problem", "ip-header-bad", "required-option-missing", "timestamp-request", "timestamp-reply", "address-mask-request", "address-mask-reply", "ping", "pong", "ttl-exceeded", ], "type": "str", }, }, "type": "dict", }, "ipsec": { "choices": ["match-ipsec", "match-none"], "type": "str", }, "limit": { "options": { "burst": {"type": "int"}, "rate": { "options": { "number": {"type": "int"}, "unit": {"type": "str"}, }, "type": "dict", }, }, "type": "dict", }, "number": {"required": True, "type": "int"}, "p2p": { "elements": "dict", "options": { "application": { "choices": [ "all", "applejuice", "bittorrent", "directconnect", "edonkey", "gnutella", "kazaa", ], "type": "str", } }, "type": "list", }, "protocol": {"type": "str"}, "recent": { "options": { "count": {"type": "int"}, "time": {"type": "int"}, }, "type": "dict", }, "source": { "options": { "address": {"type": "str"}, "group": { "options": { "address_group": { "type": "str" }, "network_group": { "type": "str" }, "port_group": {"type": "str"}, }, "type": "dict", }, "mac_address": {"type": "str"}, "port": {"type": "str"}, }, "type": "dict", }, "state": { "options": { "established": {"type": "bool"}, "invalid": {"type": "bool"}, "new": {"type": "bool"}, "related": {"type": "bool"}, }, "type": "dict", }, "tcp": { "options": {"flags": {"type": "str"}}, "type": "dict", }, "time": { "options": { "monthdays": {"type": "str"}, "startdate": {"type": "str"}, "starttime": {"type": "str"}, "stopdate": {"type": "str"}, "stoptime": {"type": "str"}, "utc": {"type": "bool"}, "weekdays": {"type": "str"}, }, "type": "dict", }, }, "type": "list", }, }, "type": "list", }, }, "type": "list", }, "running_config": {"type": "str"}, "state": { "choices": [ "merged", "replaced", "overridden", "deleted", "gathered", "rendered", "parsed", ], "default": "merged", "type": "str", }, } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py b/plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py index fd8b612..9ae4c65 100644 --- a/plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py +++ b/plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py @@ -1,78 +1,77 @@ # 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 - """ + """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", }, "running_config": {"type": "str"}, "state": { "choices": [ "merged", "replaced", "overridden", "deleted", "rendered", "parsed", "gathered", ], "default": "merged", "type": "str", }, } # pylint: disable=C0301 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 index 2f1dfe4..d6d9e29 100644 --- a/plugins/module_utils/network/vyos/argspec/l3_interfaces/l3_interfaces.py +++ b/plugins/module_utils/network/vyos/argspec/l3_interfaces/l3_interfaces.py @@ -1,89 +1,88 @@ # # -*- 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 - """ + """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", }, "running_config": {"type": "str"}, "state": { "choices": [ "merged", "replaced", "overridden", "deleted", "rendered", "gathered", "parsed", ], "default": "merged", "type": "str", }, } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/argspec/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/vyos/argspec/lag_interfaces/lag_interfaces.py index cc124a9..14ac795 100644 --- a/plugins/module_utils/network/vyos/argspec/lag_interfaces/lag_interfaces.py +++ b/plugins/module_utils/network/vyos/argspec/lag_interfaces/lag_interfaces.py @@ -1,88 +1,87 @@ # 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_lag_interfaces module """ from __future__ import absolute_import, division, print_function __metaclass__ = type class Lag_interfacesArgs(object): # pylint: disable=R0903 - """The arg spec for the vyos_lag_interfaces module - """ + """The arg spec for the vyos_lag_interfaces module""" def __init__(self, **kwargs): pass argument_spec = { "config": { "elements": "dict", "options": { "arp_monitor": { "options": { "interval": {"type": "int"}, "target": {"type": "list", "elements": "str"}, }, "type": "dict", }, "hash_policy": { "choices": ["layer2", "layer2+3", "layer3+4"], "type": "str", }, "members": { "elements": "dict", "options": {"member": {"type": "str"}}, "type": "list", }, "mode": { "choices": [ "802.3ad", "active-backup", "broadcast", "round-robin", "transmit-load-balance", "adaptive-load-balance", "xor-hash", ], "type": "str", }, "name": {"required": True, "type": "str"}, "primary": {"type": "str"}, }, "type": "list", }, "running_config": {"type": "str"}, "state": { "choices": [ "merged", "replaced", "overridden", "deleted", "rendered", "gathered", "parsed", ], "default": "merged", "type": "str", }, } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/argspec/lldp_global/lldp_global.py b/plugins/module_utils/network/vyos/argspec/lldp_global/lldp_global.py index fa41127..635322b 100644 --- a/plugins/module_utils/network/vyos/argspec/lldp_global/lldp_global.py +++ b/plugins/module_utils/network/vyos/argspec/lldp_global/lldp_global.py @@ -1,64 +1,63 @@ # 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_lldp_global module """ from __future__ import absolute_import, division, print_function __metaclass__ = type class Lldp_globalArgs(object): # pylint: disable=R0903 - """The arg spec for the vyos_lldp_global module - """ + """The arg spec for the vyos_lldp_global module""" def __init__(self, **kwargs): pass argument_spec = { "config": { "options": { "address": {"type": "str"}, "enable": {"type": "bool"}, "legacy_protocols": { "choices": ["cdp", "edp", "fdp", "sonmp"], "type": "list", "elements": "str", }, "snmp": {"type": "str"}, }, "type": "dict", }, "running_config": {"type": "str"}, "state": { "choices": [ "merged", "replaced", "deleted", "rendered", "parsed", "gathered", ], "default": "merged", "type": "str", }, } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/argspec/lldp_interfaces/lldp_interfaces.py b/plugins/module_utils/network/vyos/argspec/lldp_interfaces/lldp_interfaces.py index 109ea43..b641aeb 100644 --- a/plugins/module_utils/network/vyos/argspec/lldp_interfaces/lldp_interfaces.py +++ b/plugins/module_utils/network/vyos/argspec/lldp_interfaces/lldp_interfaces.py @@ -1,98 +1,97 @@ # # -*- 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_lldp_interfaces module """ from __future__ import absolute_import, division, print_function __metaclass__ = type class Lldp_interfacesArgs(object): # pylint: disable=R0903 - """The arg spec for the vyos_lldp_interfaces module - """ + """The arg spec for the vyos_lldp_interfaces module""" def __init__(self, **kwargs): pass argument_spec = { "config": { "elements": "dict", "options": { "enable": {"default": True, "type": "bool"}, "location": { "options": { "civic_based": { "options": { "ca_info": { "elements": "dict", "options": { "ca_type": {"type": "int"}, "ca_value": {"type": "str"}, }, "type": "list", }, "country_code": { "required": True, "type": "str", }, }, "type": "dict", }, "coordinate_based": { "options": { "altitude": {"type": "int"}, "datum": { "choices": ["WGS84", "NAD83", "MLLW"], "type": "str", }, "latitude": {"required": True, "type": "str"}, "longitude": {"required": True, "type": "str"}, }, "type": "dict", }, "elin": {"type": "str"}, }, "type": "dict", }, "name": {"required": True, "type": "str"}, }, "type": "list", }, "running_config": {"type": "str"}, "state": { "choices": [ "merged", "replaced", "overridden", "deleted", "rendered", "gathered", "parsed", ], "default": "merged", "type": "str", }, } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py index b113035..b8e915c 100644 --- a/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py +++ b/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py @@ -1,276 +1,275 @@ # # -*- 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) from __future__ import absolute_import, division, print_function __metaclass__ = type ############################################# # 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_ospfv2 module """ class Ospfv2Args(object): # pylint: disable=R0903 - """The arg spec for the vyos_ospfv2 module - """ + """The arg spec for the vyos_ospfv2 module""" def __init__(self, **kwargs): pass argument_spec = { "config": { "options": { "auto_cost": { "options": {"reference_bandwidth": {"type": "int"}}, "type": "dict", }, "default_information": { "options": { "originate": { "options": { "always": {"type": "bool"}, "metric": {"type": "int"}, "metric_type": {"type": "int"}, "route_map": {"type": "str"}, }, "type": "dict", } }, "type": "dict", }, "default_metric": {"type": "int"}, "distance": { "options": { "global": {"type": "int"}, "ospf": { "options": { "external": {"type": "int"}, "inter_area": {"type": "int"}, "intra_area": {"type": "int"}, }, "type": "dict", }, }, "type": "dict", }, "log_adjacency_changes": { "choices": ["detail"], "type": "str", }, "max_metric": { "options": { "router_lsa": { "options": { "administrative": {"type": "bool"}, "on_shutdown": {"type": "int"}, "on_startup": {"type": "int"}, }, "type": "dict", } }, "type": "dict", }, "mpls_te": { "options": { "enabled": {"type": "bool"}, "router_address": {"type": "str"}, }, "type": "dict", }, "neighbor": { "elements": "dict", "options": { "neighbor_id": {"type": "str"}, "poll_interval": {"type": "int"}, "priority": {"type": "int"}, }, "type": "list", }, "areas": { "elements": "dict", "options": { "area_id": {"type": "str"}, "area_type": { "options": { "normal": {"type": "bool"}, "nssa": { "options": { "default_cost": {"type": "int"}, "no_summary": {"type": "bool"}, "set": {"type": "bool"}, "translate": { "choices": [ "always", "candidate", "never", ], "type": "str", }, }, "type": "dict", }, "stub": { "options": { "default_cost": {"type": "int"}, "no_summary": {"type": "bool"}, "set": {"type": "bool"}, }, "type": "dict", }, }, "type": "dict", }, "authentication": { "choices": ["plaintext-password", "md5"], "type": "str", }, "network": { "elements": "dict", "options": { "address": {"required": True, "type": "str"} }, "type": "list", }, "range": { "elements": "dict", "options": { "address": {"type": "str"}, "cost": {"type": "int"}, "not_advertise": {"type": "bool"}, "substitute": {"type": "str"}, }, "type": "list", }, "shortcut": { "choices": ["default", "disable", "enable"], "type": "str", }, "virtual_link": { "elements": "dict", "options": { "address": {"type": "str"}, "authentication": { "options": { "md5": { "elements": "dict", "options": { "key_id": {"type": "int"}, "md5_key": {"type": "str"}, }, "type": "list", }, "plaintext_password": {"type": "str"}, }, "type": "dict", }, "dead_interval": {"type": "int"}, "hello_interval": {"type": "int"}, "retransmit_interval": {"type": "int"}, "transmit_delay": {"type": "int"}, }, "type": "list", }, }, "type": "list", }, "parameters": { "options": { "abr_type": { "choices": [ "cisco", "ibm", "shortcut", "standard", ], "type": "str", }, "opaque_lsa": {"type": "bool"}, "rfc1583_compatibility": {"type": "bool"}, "router_id": {"type": "str"}, }, "type": "dict", }, "passive_interface": {"type": "list", "elements": "str"}, "passive_interface_exclude": { "type": "list", "elements": "str", }, "redistribute": { "elements": "dict", "options": { "metric": {"type": "int"}, "metric_type": {"type": "int"}, "route_map": {"type": "str"}, "route_type": { "choices": [ "bgp", "connected", "kernel", "rip", "static", ], "type": "str", }, }, "type": "list", }, "route_map": {"type": "list", "elements": "str"}, "timers": { "options": { "refresh": { "options": {"timers": {"type": "int"}}, "type": "dict", }, "throttle": { "options": { "spf": { "options": { "delay": {"type": "int"}, "initial_holdtime": {"type": "int"}, "max_holdtime": {"type": "int"}, }, "type": "dict", } }, "type": "dict", }, }, "type": "dict", }, }, "type": "dict", }, "running_config": {"type": "str"}, "state": { "choices": [ "merged", "replaced", "deleted", "parsed", "gathered", "rendered", ], "default": "merged", "type": "str", }, } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py index 25f979c..7fac36c 100644 --- a/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py +++ b/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py @@ -1,98 +1,97 @@ # # -*- 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) from __future__ import absolute_import, division, print_function __metaclass__ = type ############################################# # 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_ospfv3 module """ class Ospfv3Args(object): # pylint: disable=R0903 - """The arg spec for the vyos_ospfv3 module - """ + """The arg spec for the vyos_ospfv3 module""" def __init__(self, **kwargs): pass argument_spec = { "config": { "options": { "areas": { "elements": "dict", "options": { "area_id": {"type": "str"}, "export_list": {"type": "str"}, "import_list": {"type": "str"}, "range": { "elements": "dict", "options": { "address": {"type": "str"}, "advertise": {"type": "bool"}, "not_advertise": {"type": "bool"}, }, "type": "list", }, }, "type": "list", }, "parameters": { "options": {"router_id": {"type": "str"}}, "type": "dict", }, "redistribute": { "elements": "dict", "options": { "route_map": {"type": "str"}, "route_type": { "choices": [ "bgp", "connected", "kernel", "ripng", "static", ], "type": "str", }, }, "type": "list", }, }, "type": "dict", }, "running_config": {"type": "str"}, "state": { "choices": [ "merged", "replaced", "deleted", "parsed", "gathered", "rendered", ], "default": "merged", "type": "str", }, } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/argspec/static_routes/static_routes.py b/plugins/module_utils/network/vyos/argspec/static_routes/static_routes.py index 8ecd955..f8602e3 100644 --- a/plugins/module_utils/network/vyos/argspec/static_routes/static_routes.py +++ b/plugins/module_utils/network/vyos/argspec/static_routes/static_routes.py @@ -1,99 +1,98 @@ # # -*- 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_static_routes module """ from __future__ import absolute_import, division, print_function __metaclass__ = type class Static_routesArgs(object): # pylint: disable=R0903 - """The arg spec for the vyos_static_routes module - """ + """The arg spec for the vyos_static_routes module""" def __init__(self, **kwargs): pass argument_spec = { "config": { "elements": "dict", "options": { "address_families": { "elements": "dict", "options": { "afi": { "choices": ["ipv4", "ipv6"], "required": True, "type": "str", }, "routes": { "elements": "dict", "options": { "blackhole_config": { "options": { "distance": {"type": "int"}, "type": {"type": "str"}, }, "type": "dict", }, "dest": {"required": True, "type": "str"}, "next_hops": { "elements": "dict", "options": { "admin_distance": {"type": "int"}, "enabled": {"type": "bool"}, "forward_router_address": { "required": True, "type": "str", }, "interface": {"type": "str"}, }, "type": "list", }, }, "type": "list", }, }, "type": "list", } }, "type": "list", }, "running_config": {"type": "str"}, "state": { "choices": [ "merged", "replaced", "overridden", "deleted", "gathered", "rendered", "parsed", ], "default": "merged", "type": "str", }, } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py b/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py index f035641..08b724a 100644 --- a/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py +++ b/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py @@ -1,810 +1,810 @@ # # -*- 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_global 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, remove_empties, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import ( Facts, ) from ansible.module_utils.six import iteritems from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( list_diff_want_only, ) class Firewall_global(ConfigBase): """ The vyos_firewall_global class """ gather_subset = [ "!all", "!min", ] gather_network_resources = [ "firewall_global", ] def __init__(self, module): super(Firewall_global, self).__init__(module) def get_firewall_global_facts(self, data=None): - """ Get the 'facts' (the current configuration) + """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_global_facts = facts["ansible_network_resources"].get( "firewall_global" ) if not firewall_global_facts: return [] return firewall_global_facts def execute_module(self): - """ Execute the module + """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_global_facts = self.get_firewall_global_facts() else: existing_firewall_global_facts = [] if self.state in self.ACTION_STATES or self.state == "rendered": commands.extend(self.set_config(existing_firewall_global_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_global_facts = self.get_firewall_global_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_global_facts( data=running_config ) else: changed_firewall_global_facts = [] if self.state in self.ACTION_STATES: result["before"] = existing_firewall_global_facts if result["changed"]: result["after"] = changed_firewall_global_facts elif self.state == "gathered": result["gathered"] = changed_firewall_global_facts result["warnings"] = warnings return result def set_config(self, existing_firewall_global_facts): - """ Collect the configuration from the args passed to the module, + """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_global_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 + """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", "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 == "deleted": commands.extend(self._state_deleted(want=None, have=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, w, h): - """ The command generator when state is replaced - :rtype: A list - :returns: the commands necessary to migrate the current configuration - to the desired configuration - """ + """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 h: commands.extend(self._state_deleted(h, w)) commands.extend(self._state_merged(w, h)) return commands def _state_merged(self, want, have): - """ The command generator when state is merged + """The command generator when state is merged :rtype: A list :returns: the commands necessary to merge the provided into the current configuration """ commands = [] commands.extend(self._add_global_attr(want, have)) return commands def _state_deleted(self, want, have): - """ The command generator when state is deleted + """The command generator when state is deleted :rtype: A list :returns: the commands necessary to remove the current configuration of the provided objects """ commands = [] b_set = ( "config_trap", "validation", "log_martians", "syn_cookies", "twa_hazards_protection", ) if want: for key, val in iteritems(want): if val and key in b_set and not have: commands.append(self._form_attr_cmd(attr=key, opr=False)) elif ( val and key in b_set and have and key in have and have[key] != val ): commands.append(self._form_attr_cmd(attr=key, opr=False)) else: commands.extend(self._render_attr_config(want, have, key)) elif not want and have: commands.append(self._compute_command(opr=False)) elif have: for key, val in iteritems(have): if val and key in b_set: commands.append(self._form_attr_cmd(attr=key, opr=False)) else: commands.extend(self._render_attr_config(want, have, key)) return commands def _render_attr_config(self, w, h, key, opr=False): """ This function invoke the function to extend commands based on the key. :param w: the desired configuration. :param h: the current configuration. :param key: attribute name :param opr: operation :return: list of commands """ commands = [] if key == "ping": commands.extend(self._render_ping(key, w, h, opr=opr)) elif key == "group": commands.extend(self._render_group(key, w, h, opr=opr)) elif key == "state_policy": commands.extend(self._render_state_policy(key, w, h, opr=opr)) elif key == "route_redirects": commands.extend(self._render_route_redirects(key, w, h, opr=opr)) return commands def _add_global_attr(self, w, h, opr=True): """ This function forms the set/delete commands based on the 'opr' type for firewall_global attributes. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated commands list. """ commands = [] w_fg = deepcopy(remove_empties(w)) l_set = ( "config_trap", "validation", "log_martians", "syn_cookies", "twa_hazards_protection", ) if w_fg: for key, val in iteritems(w_fg): if ( opr and key in l_set and not (h and self._is_w_same(w_fg, h, key)) ): commands.append( self._form_attr_cmd( attr=key, val=self._bool_to_str(val), opr=opr ) ) elif not opr: if key and self._is_del(l_set, h): commands.append( self._form_attr_cmd( attr=key, key=self._bool_to_str(val), opr=opr ) ) continue if ( key in l_set and not (h and self._in_target(h, key)) and not self._is_del(l_set, h) ): commands.append( self._form_attr_cmd( attr=key, val=self._bool_to_str(val), opr=opr ) ) else: commands.extend( self._render_attr_config(w_fg, h, key, opr) ) return commands def _render_ping(self, attr, w, h, opr): """ This function forms the commands for 'ping' attributes based on the 'opr'. :param attr: attribute name. :param w: the desired configuration. :param h: the target config. :param opr: True/False. :return: generated list of commands. """ commands = [] h_ping = {} l_set = ("all", "broadcast") if h: h_ping = h.get(attr) or {} if self._is_root_del(w[attr], h_ping, attr): for item, value in iteritems(h[attr]): if not opr and item in l_set: commands.append(self._form_attr_cmd(attr=item, opr=opr)) elif w[attr]: if h and attr in h.keys(): h_ping = h.get(attr) or {} for item, value in iteritems(w[attr]): if ( opr and item in l_set and not (h_ping and self._is_w_same(w[attr], h_ping, item)) ): commands.append( self._form_attr_cmd( attr=item, val=self._bool_to_str(value), opr=opr ) ) elif ( not opr and item in l_set and not (h_ping and self._is_w_same(w[attr], h_ping, item)) ): commands.append(self._form_attr_cmd(attr=item, opr=opr)) return commands def _render_group(self, attr, w, h, opr): """ This function forms the commands for 'group' attribute based on the 'opr'. :param attr: attribute name. :param w: base config. :param h: target config. :param opr: True/False. :return: generated list of commands. """ commands = [] h_grp = {} if not opr and self._is_root_del(h, w, attr): commands.append(self._form_attr_cmd(attr=attr, opr=opr)) else: if h: h_grp = h.get("group") or {} if w: commands.extend( self._render_grp_mem("port-group", w["group"], h_grp, opr) ) commands.extend( self._render_grp_mem( "address_group", w["group"], h_grp, opr ) ) commands.extend( self._render_grp_mem( "network_group", w["group"], h_grp, opr ) ) return commands def _render_grp_mem(self, attr, w, h, opr): """ This function forms the commands for group list/members attributes based on the 'opr'. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated list of commands. """ commands = [] h_grp = [] w_grp = [] l_set = ("name", "description") if w: w_grp = w.get(attr) or [] if h: h_grp = h.get(attr) or [] if w_grp: for want in w_grp: cmd = self._compute_command(key="group", attr=attr, opr=opr) h = self.search_attrib_in_have(h_grp, want, "name") for key, val in iteritems(want): if val: if ( opr and key in l_set and not (h and self._is_w_same(want, h, key)) ): if key == "name": commands.append(cmd + " " + str(val)) else: commands.append( cmd + " " + want["name"] + " " + key + " '" + str(want[key]) + "'" ) elif not opr and key in l_set: if key == "name" and self._is_grp_del( h, want, key ): commands.append(cmd + " " + want["name"]) continue if not ( h and self._in_target(h, key) ) and not self._is_grp_del(h, want, key): commands.append( cmd + " " + want["name"] + " " + key ) elif key == "members": commands.extend( self._render_ports_addrs( key, want, h, opr, cmd, want["name"], attr ) ) return commands def _render_ports_addrs(self, attr, w, h, opr, cmd, name, type): """ This function forms the commands for port/address/network group members based on the 'opr'. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param cmd: commands to be prepend. :param name: name of group. :param type: group type. :return: generated list of commands. """ commands = [] have = [] if w: want = w.get(attr) or [] if h: have = h.get(attr) or [] if want: if opr: members = list_diff_want_only(want, have) for member in members: commands.append( cmd + " " + name + " " + self._grp_type(type) + " " + member[self._get_mem_type(type)] ) elif not opr and have: members = list_diff_want_only(want, have) for member in members: commands.append( cmd + " " + name + " " + self._grp_type(type) + " " + member[self._get_mem_type(type)] ) return commands def _get_mem_type(self, group): """ This function returns the member type based on the type of group. """ return "port" if group == "port_group" else "address" def _render_state_policy(self, attr, w, h, opr): """ This function forms the commands for 'state-policy' attributes based on the 'opr'. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated list of commands. """ commands = [] have = [] l_set = ("log", "action", "connection_type") if not opr and self._is_root_del(h, w, attr): commands.append(self._form_attr_cmd(attr=attr, opr=opr)) else: w_sp = deepcopy(remove_empties(w)) want = w_sp.get(attr) or [] if h: have = h.get(attr) or [] if want: for w in want: h = self.search_attrib_in_have(have, w, "connection_type") for key, val in iteritems(w): if val and key != "connection_type": if ( opr and key in l_set and not (h and self._is_w_same(w, h, key)) ): commands.append( self._form_attr_cmd( key=attr + " " + w["connection_type"], attr=key, val=self._bool_to_str(val), opr=opr, ) ) elif not opr and key in l_set: if not ( h and self._in_target(h, key) ) and not self._is_del(l_set, h): if key == "action": commands.append( self._form_attr_cmd( attr=attr + " " + w["connection_type"], opr=opr, ) ) else: commands.append( self._form_attr_cmd( attr=attr + " " + w["connection_type"], val=self._bool_to_str(val), opr=opr, ) ) return commands def _render_route_redirects(self, attr, w, h, opr): """ This function forms the commands for 'route_redirects' attributes based on the 'opr'. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated list of commands. """ commands = [] have = [] l_set = ("afi", "ip_src_route") if w: want = w.get(attr) or [] if h: have = h.get(attr) or [] if want: for w in want: h = self.search_attrib_in_have(have, w, "afi") for key, val in iteritems(w): if val and key != "afi": if ( opr and key in l_set and not (h and self._is_w_same(w, h, key)) ): commands.append( self._form_attr_cmd( attr=key, val=self._bool_to_str(val), opr=opr, ) ) elif not opr and key in l_set: if self._is_del(l_set, h): commands.append( self._form_attr_cmd( attr=key, val=self._bool_to_str(val), opr=opr, ) ) continue if not ( h and self._in_target(h, key) ) and not self._is_del(l_set, h): commands.append( self._form_attr_cmd( attr=key, val=self._bool_to_str(val), opr=opr, ) ) elif key == "icmp_redirects": commands.extend( self._render_icmp_redirects(key, w, h, opr) ) return commands def _render_icmp_redirects(self, attr, w, h, opr): """ This function forms the commands for 'icmp_redirects' attributes based on the 'opr'. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated list of commands. """ commands = [] h_red = {} l_set = ("send", "receive") if w[attr]: if h and attr in h.keys(): h_red = h.get(attr) or {} for item, value in iteritems(w[attr]): if ( opr and item in l_set and not (h_red and self._is_w_same(w[attr], h_red, item)) ): commands.append( self._form_attr_cmd( attr=item, val=self._bool_to_str(value), opr=opr ) ) elif ( not opr and item in l_set and not (h_red and self._is_w_same(w[attr], h_red, item)) ): commands.append(self._form_attr_cmd(attr=item, opr=opr)) return commands def search_attrib_in_have(self, have, want, attr): """ This function returns the attribute if it is present in target config. :param have: the target config. :param want: the desired config. :param attr: attribute name . :return: attribute/None """ if have: for h in have: if h[attr] == want[attr]: return h return None def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True): """ This function forms the command for leaf attribute. :param key: parent key. :param attr: attribute name :param value: value :param opr: True/False. :return: generated command. """ command = self._compute_command( key=key, attr=self._map_attrib(attr), val=val, opr=opr ) return command def _compute_command( self, key=None, attr=None, val=None, remove=False, opr=True ): """ This function construct the add/delete command based on passed attributes. :param key: parent key. :param attr: attribute name :param value: value :param remove: True/False. :param opr: True/False. :return: generated command. """ if remove or not opr: cmd = "delete firewall " else: cmd = "set firewall " if key: cmd += key.replace("_", "-") + " " if attr: cmd += attr.replace("_", "-") if val and opr: cmd += " '" + str(val) + "'" return cmd def _bool_to_str(self, val): """ This function converts the bool value into string. :param val: bool value. :return: enable/disable. """ return ( "enable" if str(val) == "True" else "disable" if str(val) == "False" else val ) def _grp_type(self, val): """ This function returns the group member type based on value argument. :param val: value. :return: member type. """ return ( "address" if val == "address_group" else "network" if val == "network_group" else "port" ) def _is_w_same(self, w, h, key): """ This function checks whether the key value is same in desired and target config dictionary. :param w: base config. :param h: target config. :param key:attribute name. :return: True/False. """ return True if h and key in h and h[key] == w[key] else False def _in_target(self, h, key): """ This function checks whether the target exist and key present in target config. :param h: target config. :param key: attribute name. :return: True/False. """ return True if h and key in h else False def _is_grp_del(self, w, h, key): """ This function checks whether group needed to be deleted based on desired and target configs. :param w: the desired config. :param h: the target config. :param key: group name. :return: True/False. """ return ( True if h and key in h and (not w or key not in w or not w[key]) else False ) def _is_root_del(self, w, h, key): """ This function checks whether a root attribute which can have further child attributes needed to be deleted. :param w: the desired config. :param h: the target config. :param key: attribute name. :return: True/False. """ return ( True if h and key in h and (not w or key not in w or not w[key]) else False ) def _is_del(self, b_set, h, key="number"): """ This function checks whether attribute needs to be deleted when operation is false and attribute present in present target config. :param b_set: attribute set. :param h: target config. :param key: number. :return: True/False. """ return key in b_set and not (h and self._in_target(h, key)) def _map_attrib(self, attrib, type=None): """ - This function construct the regex string. - replace the underscore with hyphen. :param attrib: attribute :return: regex string """ regex = attrib.replace("_", "-") if attrib == "send": if type == "ipv6": regex = "ipv6-send-redirects" else: regex = "send-redirects" elif attrib == "ip_src_route": if type == "ipv6": regex = "ipv6-src-route" elif attrib == "receive": if type == "ipv6": regex = "ipv6-receive-redirects" else: regex = "receive-redirects" elif attrib == "disabled": regex = "disable" elif attrib == "all": regex = "all-ping" elif attrib == "broadcast": regex = "broadcast-ping" elif attrib == "validation": regex = "source-validation" return regex 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 index bd2fbab..45842f1 100644 --- a/plugins/module_utils/network/vyos/config/firewall_interfaces/firewall_interfaces.py +++ b/plugins/module_utils/network/vyos/config/firewall_interfaces/firewall_interfaces.py @@ -1,454 +1,454 @@ # # -*- 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, 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) + """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 + """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 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 + """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 + """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 + """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 + """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 + """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/config/firewall_rules/firewall_rules.py b/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py index 5e38cee..ffc642f 100644 --- a/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py +++ b/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py @@ -1,1017 +1,1017 @@ # # -*- 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_rules 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, remove_empties, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import ( Facts, ) from ansible.module_utils.six import iteritems from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( list_diff_want_only, ) class Firewall_rules(ConfigBase): """ The vyos_firewall_rules class """ gather_subset = [ "!all", "!min", ] gather_network_resources = [ "firewall_rules", ] def __init__(self, module): super(Firewall_rules, self).__init__(module) def get_firewall_rules_facts(self, data=None): - """ Get the 'facts' (the current configuration) + """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_rules_facts = facts["ansible_network_resources"].get( "firewall_rules" ) if not firewall_rules_facts: return [] return firewall_rules_facts def execute_module(self): - """ Execute the module + """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_rules_facts = self.get_firewall_rules_facts() else: existing_firewall_rules_facts = [] if self.state in self.ACTION_STATES or self.state == "rendered": commands.extend(self.set_config(existing_firewall_rules_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_rules_facts = self.get_firewall_rules_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_rules_facts( data=running_config ) else: changed_firewall_rules_facts = [] if self.state in self.ACTION_STATES: result["before"] = existing_firewall_rules_facts if result["changed"]: result["after"] = changed_firewall_rules_facts elif self.state == "gathered": result["gathered"] = changed_firewall_rules_facts result["warnings"] = warnings return result def set_config(self, existing_firewall_rules_facts): - """ Collect the configuration from the args passed to the module, + """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_rules_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 + """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 - """ + """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: r_sets = self._get_r_sets(h) for rs in r_sets: w = self.search_r_sets_in_have(want, rs["name"], "r_list") commands.extend( self._add_r_sets(h["afi"], rs, 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 + """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 in have: r_sets = self._get_r_sets(h) for rs in r_sets: w = self.search_r_sets_in_have(want, rs["name"], "r_list") if not w: commands.append( self._compute_command( h["afi"], rs["name"], remove=True ) ) else: commands.extend( self._add_r_sets(h["afi"], rs, w, opr=False) ) commands.extend(self._state_merged(want, have)) return commands def _state_merged(self, want, have): - """ The command generator when state is merged + """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: r_sets = self._get_r_sets(w) for rs in r_sets: h = self.search_r_sets_in_have(have, rs["name"], "r_list") commands.extend(self._add_r_sets(w["afi"], rs, h)) return commands def _state_deleted(self, want, have): - """ The command generator when state is deleted + """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: r_sets = self._get_r_sets(w) if r_sets: for rs in r_sets: h = self.search_r_sets_in_have( have, rs["name"], "r_list" ) if h: commands.append( self._compute_command( w["afi"], h["name"], remove=True ) ) elif have: for h in have: if h["afi"] == w["afi"]: commands.append( self._compute_command(w["afi"], remove=True) ) elif have: for h in have: r_sets = self._get_r_sets(h) if r_sets: commands.append( self._compute_command(afi=h["afi"], remove=True) ) return commands def _add_r_sets(self, afi, want, have, opr=True): """ This function forms the set/delete commands based on the 'opr' type for rule-sets attributes. :param afi: address type. :param want: desired config. :param have: target config. :param opr: True/False. :return: generated commands list. """ commands = [] l_set = ("description", "default_action", "enable_default_log") h_rs = {} h_rules = {} w_rs = deepcopy(remove_empties(want)) w_rules = w_rs.pop("rules", None) if have: h_rs = deepcopy(remove_empties(have)) h_rules = h_rs.pop("rules", None) if w_rs: for key, val in iteritems(w_rs): if ( opr and key in l_set and not (h_rs and self._is_w_same(w_rs, h_rs, key)) ): if key == "enable_default_log": if val and ( not h_rs or key not in h_rs or not h_rs[key] ): commands.append( self._add_rs_base_attrib( afi, want["name"], key, w_rs ) ) else: commands.append( self._add_rs_base_attrib( afi, want["name"], key, w_rs ) ) elif not opr and key in l_set: if ( key == "enable_default_log" and val and h_rs and (key not in h_rs or not h_rs[key]) ): commands.append( self._add_rs_base_attrib( afi, want["name"], key, w_rs, opr ) ) elif not (h_rs and self._in_target(h_rs, key)): commands.append( self._add_rs_base_attrib( afi, want["name"], key, w_rs, opr ) ) commands.extend( self._add_rules(afi, want["name"], w_rules, h_rules, opr) ) if h_rules: have["rules"] = h_rules if w_rules: want["rules"] = w_rules return commands def _add_rules(self, afi, name, w_rules, h_rules, opr=True): """ This function forms the set/delete commands based on the 'opr' type for rules attributes. :param want: desired config. :param have: target config. :return: generated commands list. """ commands = [] l_set = ( "ipsec", "action", "number", "protocol", "fragment", "disabled", "description", ) if w_rules: for w in w_rules: cmd = self._compute_command(afi, name, w["number"], opr=opr) h = self.search_r_sets_in_have( h_rules, w["number"], type="rules" ) for key, val in iteritems(w): if val: if ( opr and key in l_set and not (h and self._is_w_same(w, h, key)) ): if key == "disabled": if not ( not val and (not h or key not in h or not h[key]) ): commands.append( self._add_r_base_attrib( afi, name, key, w ) ) else: commands.append( self._add_r_base_attrib(afi, name, key, w) ) elif not opr: if key == "number" and self._is_del(l_set, h): commands.append( self._add_r_base_attrib( afi, name, key, w, opr=opr ) ) continue if ( key == "disabled" and val and h and (key not in h or not h[key]) ): commands.append( self._add_r_base_attrib( afi, name, key, w, opr=opr ) ) elif ( key in l_set and not (h and self._in_target(h, key)) and not self._is_del(l_set, h) ): commands.append( self._add_r_base_attrib( afi, name, key, w, opr=opr ) ) elif key == "p2p": commands.extend(self._add_p2p(key, w, h, cmd, opr)) elif key == "tcp": commands.extend(self._add_tcp(key, w, h, cmd, opr)) elif key == "time": commands.extend( self._add_time(key, w, h, cmd, opr) ) elif key == "icmp": commands.extend( self._add_icmp(key, w, h, cmd, opr) ) elif key == "state": commands.extend( self._add_state(key, w, h, cmd, opr) ) elif key == "limit": commands.extend( self._add_limit(key, w, h, cmd, opr) ) elif key == "recent": commands.extend( self._add_recent(key, w, h, cmd, opr) ) elif key == "destination" or key == "source": commands.extend( self._add_src_or_dest(key, w, h, cmd, opr) ) return commands def _add_p2p(self, attr, w, h, cmd, opr): """ This function forms the set/delete commands based on the 'opr' type for p2p applications attributes. :param want: desired config. :param have: target config. :return: generated commands list. """ commands = [] have = [] if w: want = w.get(attr) or [] if h: have = h.get(attr) or [] if want: if opr: applications = list_diff_want_only(want, have) for app in applications: commands.append( cmd + (" " + attr + " " + app["application"]) ) elif not opr and have: applications = list_diff_want_only(want, have) for app in applications: commands.append( cmd + (" " + attr + " " + app["application"]) ) return commands def _add_state(self, attr, w, h, cmd, opr): """ This function forms the command for 'state' attributes based on the 'opr'. :param attr: attribute name. :param w: base config. :param h: target config. :param cmd: commands to be prepend. :return: generated list of commands. """ h_state = {} commands = [] l_set = ("new", "invalid", "related", "established") if w[attr]: if h and attr in h.keys(): h_state = h.get(attr) or {} for item, val in iteritems(w[attr]): if ( opr and item in l_set and not ( h_state and self._is_w_same(w[attr], h_state, item) ) ): commands.append( cmd + ( " " + attr + " " + item + " " + self._bool_to_str(val) ) ) elif ( not opr and item in l_set and not (h_state and self._in_target(h_state, item)) ): commands.append(cmd + (" " + attr + " " + item)) return commands def _add_recent(self, attr, w, h, cmd, opr): """ This function forms the command for 'recent' attributes based on the 'opr'. :param attr: attribute name. :param w: base config. :param h: target config. :param cmd: commands to be prepend. :return: generated list of commands. """ commands = [] h_recent = {} l_set = ("count", "time") if w[attr]: if h and attr in h.keys(): h_recent = h.get(attr) or {} for item, val in iteritems(w[attr]): if ( opr and item in l_set and not ( h_recent and self._is_w_same(w[attr], h_recent, item) ) ): commands.append( cmd + (" " + attr + " " + item + " " + str(val)) ) elif ( not opr and item in l_set and not (h_recent and self._in_target(h_recent, item)) ): commands.append(cmd + (" " + attr + " " + item)) return commands def _add_icmp(self, attr, w, h, cmd, opr): """ This function forms the commands for 'icmp' attributes based on the 'opr'. :param attr: attribute name. :param w: base config. :param h: target config. :param cmd: commands to be prepend. :return: generated list of commands. """ commands = [] h_icmp = {} l_set = ("code", "type", "type_name") if w[attr]: if h and attr in h.keys(): h_icmp = h.get(attr) or {} for item, val in iteritems(w[attr]): if ( opr and item in l_set and not (h_icmp and self._is_w_same(w[attr], h_icmp, item)) ): if item == "type_name": if "ipv6-name" in cmd: commands.append( cmd + (" " + "icmpv6" + " " + "type" + " " + val) ) else: commands.append( cmd + ( " " + attr + " " + item.replace("_", "-") + " " + val ) ) else: commands.append( cmd + (" " + attr + " " + item + " " + str(val)) ) elif ( not opr and item in l_set and not (h_icmp and self._in_target(h_icmp, item)) ): commands.append(cmd + (" " + attr + " " + item)) return commands def _add_time(self, attr, w, h, cmd, opr): """ This function forms the commands for 'time' attributes based on the 'opr'. :param attr: attribute name. :param w: base config. :param h: target config. :param cmd: commands to be prepend. :return: generated list of commands. """ commands = [] h_time = {} l_set = ( "utc", "stopdate", "stoptime", "weekdays", "monthdays", "startdate", "starttime", ) if w[attr]: if h and attr in h.keys(): h_time = h.get(attr) or {} for item, val in iteritems(w[attr]): if ( opr and item in l_set and not (h_time and self._is_w_same(w[attr], h_time, item)) ): if item == "utc": if not ( not val and (not h_time or item not in h_time) ): commands.append(cmd + (" " + attr + " " + item)) else: commands.append( cmd + (" " + attr + " " + item + " " + val) ) elif ( not opr and item in l_set and not (h_time and self._is_w_same(w[attr], h_time, item)) ): commands.append(cmd + (" " + attr + " " + item)) return commands def _add_tcp(self, attr, w, h, cmd, opr): """ This function forms the commands for 'tcp' attributes based on the 'opr'. :param attr: attribute name. :param w: base config. :param h: target config. :param cmd: commands to be prepend. :return: generated list of commands. """ h_tcp = {} commands = [] if w[attr]: key = "flags" flags = w[attr].get(key) or {} if flags: if h and key in h[attr].keys(): h_tcp = h[attr].get(key) or {} if flags: if opr and not ( h_tcp and self._is_w_same(w[attr], h[attr], key) ): commands.append( cmd + (" " + attr + " " + key + " " + flags) ) if not opr and not ( h_tcp and self._is_w_same(w[attr], h[attr], key) ): commands.append( cmd + (" " + attr + " " + key + " " + flags) ) return commands def _add_limit(self, attr, w, h, cmd, opr): """ This function forms the commands for 'limit' attributes based on the 'opr'. :param attr: attribute name. :param w: base config. :param h: target config. :param cmd: commands to be prepend. :return: generated list of commands. """ h_limit = {} commands = [] if w[attr]: key = "burst" if ( opr and key in w[attr].keys() and not ( h and attr in h.keys() and self._is_w_same(w[attr], h[attr], key) ) ): commands.append( cmd + (" " + attr + " " + key + " " + str(w[attr].get(key))) ) elif ( not opr and key in w[attr].keys() and not ( h and attr in h.keys() and self._in_target(h[attr], key) ) ): commands.append( cmd + (" " + attr + " " + key + " " + str(w[attr].get(key))) ) key = "rate" rate = w[attr].get(key) or {} if rate: if h and key in h[attr].keys(): h_limit = h[attr].get(key) or {} if "unit" in rate and "number" in rate: if opr and not ( h_limit and self._is_w_same(rate, h_limit, "unit") and self.is_w_same(rate, h_limit, "number") ): commands.append( cmd + ( " " + attr + " " + key + " " + str(rate["number"]) + "/" + rate["unit"] ) ) if not opr and not ( h_limit and self._is_w_same(rate, h_limit, "unit") and self._is_w_same(rate, h_limit, "number") ): commands.append(cmd + (" " + attr + " " + key)) return commands def _add_src_or_dest(self, attr, w, h, cmd, opr=True): """ This function forms the commands for 'src/dest' attributes based on the 'opr'. :param attr: attribute name. :param w: base config. :param h: target config. :param cmd: commands to be prepend. :return: generated list of commands. """ commands = [] h_group = {} g_set = ("port_group", "address_group", "network_group") if w[attr]: keys = ("address", "mac_address", "port") for key in keys: if ( opr and key in w[attr].keys() and not ( h and attr in h.keys() and self._is_w_same(w[attr], h[attr], key) ) ): commands.append( cmd + ( " " + attr + " " + key.replace("_", "-") + " " + w[attr].get(key) ) ) elif ( not opr and key in w[attr].keys() and not ( h and attr in h.keys() and self._in_target(h[attr], key) ) ): commands.append(cmd + (" " + attr + " " + key)) key = "group" group = w[attr].get(key) or {} if group: if h and key in h[attr].keys(): h_group = h[attr].get(key) or {} for item, val in iteritems(group): if val: if ( opr and item in g_set and not ( h_group and self._is_w_same(group, h_group, item) ) ): commands.append( cmd + ( " " + attr + " " + key + " " + item.replace("_", "-") + " " + val ) ) elif ( not opr and item in g_set and not ( h_group and self._in_target(h_group, item) ) ): commands.append( cmd + ( " " + attr + " " + key + " " + item.replace("_", "-") ) ) return commands def search_r_sets_in_have(self, have, w_name, type="rule_sets"): """ This function returns the rule-set/rule if it is present in target config. :param have: target config. :param w_name: rule-set name. :param type: rule_sets/rule/r_list. :return: rule-set/rule. """ if have: key = "name" if type == "rules": key = "number" for r in have: if r[key] == w_name: return r elif type == "r_list": for h in have: r_sets = self._get_r_sets(h) for rs in r_sets: if rs[key] == w_name: return rs else: for rs in have: if rs[key] == w_name: return rs return None def _get_r_sets(self, item, type="rule_sets"): """ This function returns the list of rule-sets/rules. :param item: config dictionary. :param type: rule_sets/rule/r_list. :return: list of rule-sets/rules. """ rs_list = [] r_sets = item[type] if r_sets: for rs in r_sets: rs_list.append(rs) return rs_list def _compute_command( self, afi, name=None, number=None, attrib=None, value=None, remove=False, opr=True, ): """ This function construct the add/delete command based on passed attributes. :param afi: address type. :param name: rule-set name. :param number: rule-number. :param attrib: attribute name. :param value: value. :param remove: True if delete command needed to be construct. :param opr: opeeration flag. :return: generated command. """ if remove or not opr: cmd = "delete firewall " + self._get_fw_type(afi) else: cmd = "set firewall " + self._get_fw_type(afi) if name: cmd += " " + name if number: cmd += " rule " + str(number) if attrib: cmd += " " + attrib.replace("_", "-") if ( value and opr and attrib != "enable_default_log" and attrib != "disabled" ): cmd += " '" + str(value) + "'" return cmd def _add_r_base_attrib(self, afi, name, attr, rule, opr=True): """ This function forms the command for 'rules' attributes which doesn't have further sub attributes. :param afi: address type. :param name: rule-set name :param attrib: attribute name :param rule: rule config dictionary. :param opr: True/False. :return: generated command. """ if attr == "number": command = self._compute_command( afi=afi, name=name, number=rule["number"], opr=opr ) else: command = self._compute_command( afi=afi, name=name, number=rule["number"], attrib=attr, value=rule[attr], opr=opr, ) return command def _add_rs_base_attrib(self, afi, name, attrib, rule, opr=True): """ This function forms the command for 'rule-sets' attributes which doesn't have further sub attributes. :param afi: address type. :param name: rule-set name :param attrib: attribute name :param rule: rule config dictionary. :param opr: True/False. :return: generated command. """ command = self._compute_command( afi=afi, name=name, attrib=attrib, value=rule[attrib], opr=opr ) return command def _bool_to_str(self, val): """ This function converts the bool value into string. :param val: bool value. :return: enable/disable. """ return "enable" if val else "disable" 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" def _is_del(self, l_set, h, key="number"): """ This function checks whether rule needs to be deleted based on the rule number. :param l_set: attribute set. :param h: target config. :param key: number. :return: True/False. """ return key in l_set and not (h and self._in_target(h, key)) def _is_w_same(self, w, h, key): """ This function checks whether the key value is same in base and target config dictionary. :param w: base config. :param h: target config. :param key:attribute name. :return: True/False. """ return True if h and key in h and h[key] == w[key] else False def _in_target(self, h, key): """ This function checks whether the target nexist and key present in target config. :param h: target config. :param key: attribute name. :return: True/False. """ return True if h and key in h else False def _is_base_attrib(self, key): """ This function checks whether key is present in predefined based attribute set. :param key: :return: True/False. """ r_set = ( "p2p", "ipsec", "action", "fragment", "protocol", "disabled", "description", "mac_address", "default_action", "enable_default_log", ) return True if key in r_set else False diff --git a/plugins/module_utils/network/vyos/config/interfaces/interfaces.py b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py index 51bf98e..86008e8 100644 --- a/plugins/module_utils/network/vyos/config/interfaces/interfaces.py +++ b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py @@ -1,351 +1,351 @@ # 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_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, ) 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, data=None): - """ Get the 'facts' (the current configuration) + """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 ) interfaces_facts = facts["ansible_network_resources"].get("interfaces") if not interfaces_facts: return [] return interfaces_facts def execute_module(self): - """ Execute the module + """Execute the module :rtype: A dictionary :returns: The result from module execution """ result = {"changed": False} commands = list() warnings = list() if self.state in self.ACTION_STATES: existing_interfaces_facts = self.get_interfaces_facts() else: existing_interfaces_facts = [] if self.state in self.ACTION_STATES or self.state == "rendered": commands.extend(self.set_config(existing_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_interfaces_facts = self.get_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_interfaces_facts(data=running_config) else: changed_interfaces_facts = [] if self.state in self.ACTION_STATES: result["before"] = existing_interfaces_facts if result["changed"]: result["after"] = changed_interfaces_facts elif self.state == "gathered": result["gathered"] = 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 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 + """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 want ): 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(want=want, have=have)) elif self.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": name} if self.state in ("merged", "rendered"): commands.extend(self._state_merged(item, obj_in_have)) elif self.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 + """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 + """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 + """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 + """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/l3_interfaces.py b/plugins/module_utils/network/vyos/config/l3_interfaces/l3_interfaces.py index 122cc1e..6f143be 100644 --- a/plugins/module_utils/network/vyos/config/l3_interfaces/l3_interfaces.py +++ b/plugins/module_utils/network/vyos/config/l3_interfaces/l3_interfaces.py @@ -1,344 +1,344 @@ # # -*- 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_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, 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, data=None): - """ Get the 'facts' (the current configuration) + """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 ) 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 + """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_l3_interfaces_facts = self.get_l3_interfaces_facts() else: existing_l3_interfaces_facts = [] if self.state in self.ACTION_STATES or self.state == "rendered": commands.extend(self.set_config(existing_l3_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_l3_interfaces_facts = self.get_l3_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_l3_interfaces_facts( data=running_config ) else: changed_l3_interfaces_facts = [] if self.state in self.ACTION_STATES: result["before"] = existing_l3_interfaces_facts if result["changed"]: result["after"] = changed_l3_interfaces_facts elif self.state == "gathered": result["gathered"] = 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 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 + """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 in ("merged", "replaced", "overridden", "rendered") and not want ): self._module.fail_json( msg="value of config parameter must not be empty for state {0}".format( 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 in ("merged", "rendered"): 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 + """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 + """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 + """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 + """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/config/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/vyos/config/lag_interfaces/lag_interfaces.py index 452670f..7e7b39a 100644 --- a/plugins/module_utils/network/vyos/config/lag_interfaces/lag_interfaces.py +++ b/plugins/module_utils/network/vyos/config/lag_interfaces/lag_interfaces.py @@ -1,468 +1,468 @@ # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The vyos_lag_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 ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( ConfigBase, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import ( Facts, ) from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( to_list, dict_diff, ) from ansible.module_utils.six import iteritems from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( search_obj_in_list, get_lst_diff_for_dicts, list_diff_want_only, list_diff_have_only, ) class Lag_interfaces(ConfigBase): """ The vyos_lag_interfaces class """ gather_subset = [ "!all", "!min", ] gather_network_resources = [ "lag_interfaces", ] params = [ "arp_monitor", "hash_policy", "members", "mode", "name", "primary", ] def __init__(self, module): super(Lag_interfaces, self).__init__(module) def get_lag_interfaces_facts(self, data=None): - """ Get the 'facts' (the current configuration) + """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 ) lag_interfaces_facts = facts["ansible_network_resources"].get( "lag_interfaces" ) if not lag_interfaces_facts: return [] return lag_interfaces_facts def execute_module(self): - """ Execute the module + """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_lag_interfaces_facts = self.get_lag_interfaces_facts() else: existing_lag_interfaces_facts = [] if self.state in self.ACTION_STATES or self.state == "rendered": commands.extend(self.set_config(existing_lag_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_lag_interfaces_facts = self.get_lag_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_lag_interfaces_facts( data=running_config ) else: changed_lag_interfaces_facts = [] if self.state in self.ACTION_STATES: result["before"] = existing_lag_interfaces_facts if result["changed"]: result["after"] = changed_lag_interfaces_facts elif self.state == "gathered": result["gathered"] = changed_lag_interfaces_facts result["warnings"] = warnings return result def set_config(self, existing_lag_interfaces_facts): - """ Collect the configuration from the args passed to the module, + """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_lag_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 + """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 want ): 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(want, have)) elif self.state == "deleted": if want: for want_item in want: name = want_item["name"] obj_in_have = search_obj_in_list(name, have) commands.extend(self._state_deleted(obj_in_have)) else: for have_item in have: commands.extend(self._state_deleted(have_item)) else: for want_item in want: name = want_item["name"] obj_in_have = search_obj_in_list(name, have) if self.state in ("merged", "rendered"): commands.extend(self._state_merged(want_item, obj_in_have)) elif self.state == "replaced": commands.extend( self._state_replaced(want_item, obj_in_have) ) return commands def _state_replaced(self, want, have): - """ The command generator when state is replaced + """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._render_del_commands(want, have)) commands.extend(self._state_merged(want, have)) return commands def _state_overridden(self, want, have): - """ The command generator when state is overridden + """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 have_item in have: lag_name = have_item["name"] obj_in_want = search_obj_in_list(lag_name, want) if not obj_in_want: commands.extend(self._purge_attribs(have_item)) for want_item in want: name = want_item["name"] obj_in_have = search_obj_in_list(name, have) commands.extend(self._state_replaced(want_item, obj_in_have)) return commands def _state_merged(self, want, have): - """ The command generator when state is merged + """The command generator when state is merged :rtype: A list :returns: the commands necessary to merge the provided into the current configuration """ commands = [] if have: commands.extend(self._render_updates(want, have)) else: commands.extend(self._render_set_commands(want)) return commands def _state_deleted(self, have): - """ The command generator when state is deleted + """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 have: commands.extend(self._purge_attribs(have)) return commands def _render_updates(self, want, have): commands = [] temp_have_members = have.pop("members", None) temp_want_members = want.pop("members", None) updates = dict_diff(have, want) if temp_have_members: have["members"] = temp_have_members if temp_want_members: want["members"] = temp_want_members commands.extend(self._add_bond_members(want, have)) if updates: for key, value in iteritems(updates): if value: if key == "arp_monitor": commands.extend( self._add_arp_monitor(updates, key, want, have) ) else: commands.append( self._compute_command( have["name"], key, str(value) ) ) return commands def _render_set_commands(self, want): commands = [] have = [] params = Lag_interfaces.params for attrib in params: value = want[attrib] if value: if attrib == "arp_monitor": commands.extend( self._add_arp_monitor(want, attrib, want, have) ) elif attrib == "members": commands.extend(self._add_bond_members(want, have)) elif attrib != "name": commands.append( self._compute_command( want["name"], attrib, value=str(value) ) ) return commands def _purge_attribs(self, have): commands = [] for item in Lag_interfaces.params: if have.get(item): if item == "members": commands.extend(self._delete_bond_members(have)) elif item != "name": commands.append( self._compute_command( have["name"], attrib=item, remove=True ) ) return commands def _render_del_commands(self, want, have): commands = [] params = Lag_interfaces.params for attrib in params: if attrib == "members": commands.extend(self._update_bond_members(attrib, want, have)) elif attrib == "arp_monitor": commands.extend(self._update_arp_monitor(attrib, want, have)) elif have.get(attrib) and not want.get(attrib): commands.append( self._compute_command(have["name"], attrib, remove=True) ) return commands def _add_bond_members(self, want, have): commands = [] diff_members = get_lst_diff_for_dicts(want, have, "members") if diff_members: for key in diff_members: commands.append( self._compute_command( key["member"], "bond-group", want["name"], type="ethernet", ) ) return commands def _add_arp_monitor(self, updates, key, want, have): commands = [] arp_monitor = updates.get(key) or {} diff_targets = self._get_arp_monitor_target_diff( want, have, key, "target" ) if "interval" in arp_monitor: commands.append( self._compute_command( key=want["name"] + " arp-monitor", attrib="interval", value=str(arp_monitor["interval"]), ) ) if diff_targets: for target in diff_targets: commands.append( self._compute_command( key=want["name"] + " arp-monitor", attrib="target", value=target, ) ) return commands def _delete_bond_members(self, have): commands = [] for member in have["members"]: commands.append( self._compute_command( member["member"], "bond-group", have["name"], remove=True, type="ethernet", ) ) return commands def _update_arp_monitor(self, key, want, have): commands = [] want_arp_target = [] have_arp_target = [] want_arp_monitor = want.get(key) or {} have_arp_monitor = have.get(key) or {} if want_arp_monitor and "target" in want_arp_monitor: want_arp_target = want_arp_monitor["target"] if have_arp_monitor and "target" in have_arp_monitor: have_arp_target = have_arp_monitor["target"] if "interval" in have_arp_monitor and not want_arp_monitor: commands.append( self._compute_command( key=have["name"] + " arp-monitor", attrib="interval", remove=True, ) ) if "target" in have_arp_monitor: target_diff = list_diff_have_only(want_arp_target, have_arp_target) if target_diff: for target in target_diff: commands.append( self._compute_command( key=have["name"] + " arp-monitor", attrib="target", value=target, remove=True, ) ) return commands def _update_bond_members(self, key, want, have): commands = [] want_members = want.get(key) or [] have_members = have.get(key) or [] members_diff = list_diff_have_only(want_members, have_members) if members_diff: for member in members_diff: commands.append( self._compute_command( member["member"], "bond-group", have["name"], True, "ethernet", ) ) return commands def _get_arp_monitor_target_diff( self, want_list, have_list, dict_name, lst ): want_arp_target = [] have_arp_target = [] want_arp_monitor = want_list.get(dict_name) or {} if want_arp_monitor and lst in want_arp_monitor: want_arp_target = want_arp_monitor[lst] if not have_list: diff = want_arp_target else: have_arp_monitor = have_list.get(dict_name) or {} if have_arp_monitor and lst in have_arp_monitor: have_arp_target = have_arp_monitor[lst] diff = list_diff_want_only(want_arp_target, have_arp_target) return diff def _compute_command( self, key, attrib, value=None, remove=False, type="bonding" ): if remove: cmd = "delete interfaces " + type else: cmd = "set interfaces " + type cmd += " " + key if attrib == "arp_monitor": attrib = "arp-monitor" elif attrib == "hash_policy": attrib = "hash-policy" cmd += " " + attrib if value: cmd += " '" + value + "'" return cmd diff --git a/plugins/module_utils/network/vyos/config/lldp_global/lldp_global.py b/plugins/module_utils/network/vyos/config/lldp_global/lldp_global.py index c70d27f..d66e9c6 100644 --- a/plugins/module_utils/network/vyos/config/lldp_global/lldp_global.py +++ b/plugins/module_utils/network/vyos/config/lldp_global/lldp_global.py @@ -1,286 +1,286 @@ # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The vyos_lldp_global 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 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, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import ( Facts, ) from ansible.module_utils.six import iteritems from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( get_lst_diff_for_dicts, list_diff_have_only, ) class Lldp_global(ConfigBase): """ The vyos_lldp_global class """ gather_subset = [ "!all", "!min", ] gather_network_resources = [ "lldp_global", ] params = ["enable", "address", "snmp", "legacy_protocols"] def __init__(self, module): super(Lldp_global, self).__init__(module) def get_lldp_global_facts(self, data=None): - """ Get the 'facts' (the current configuration) + """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 ) lldp_global_facts = facts["ansible_network_resources"].get( "lldp_global" ) if not lldp_global_facts: return [] return lldp_global_facts def execute_module(self): - """ Execute the module + """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_lldp_global_facts = self.get_lldp_global_facts() else: existing_lldp_global_facts = [] if self.state in self.ACTION_STATES or self.state == "rendered": commands.extend(self.set_config(existing_lldp_global_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_lldp_global_facts = self.get_lldp_global_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_lldp_global_facts(data=running_config) else: changed_lldp_global_facts = [] if self.state in self.ACTION_STATES: result["before"] = existing_lldp_global_facts if result["changed"]: result["after"] = changed_lldp_global_facts elif self.state == "gathered": result["gathered"] = changed_lldp_global_facts result["warnings"] = warnings return result def set_config(self, existing_lldp_global_facts): - """ Collect the configuration from the args passed to the module, + """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_lldp_global_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 + """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", "rendered") and not want: self._module.fail_json( msg="value of config parameter must not be empty for state {0}".format( self.state ) ) if self.state == "deleted": commands.extend(self._state_deleted(want=None, have=have)) elif self.state in ("merged", "rendered"): commands.extend(self._state_merged(want=want, have=have)) elif self.state == "replaced": commands.extend(self._state_replaced(want=want, have=have)) return commands def _state_replaced(self, want, have): - """ The command generator when state is replaced + """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_merged(self, want, have): - """ The command generator when state is merged + """The command generator when state is merged :rtype: A list :returns: the commands necessary to merge the provided into the current configuration """ commands = [] commands.extend(self._render_updates(want, have)) return commands def _state_deleted(self, want, have): - """ The command generator when state is deleted + """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 item in Lldp_global.params: if item == "legacy_protocols": commands.extend(self._update_lldp_protocols(want, have)) elif ( have.get(item) and not want.get(item) and item != "enable" ): commands.append(Lldp_global.del_cmd + item) elif have: for item in Lldp_global.params: if have.get(item): if item == "legacy_protocols": commands.append( self._compute_command( "legacy-protocols", remove=True ) ) elif item == "address": commands.append( self._compute_command( "management-address", remove=True ) ) elif item == "snmp": commands.append( self._compute_command(item, remove=True) ) return commands def _render_updates(self, want, have): commands = [] if have: temp_have_legacy_protos = have.pop("legacy_protocols", None) else: have = {} temp_want_legacy_protos = want.pop("legacy_protocols", None) updates = dict_diff(have, want) if have and temp_have_legacy_protos: have["legacy_protocols"] = temp_have_legacy_protos if not have and temp_want_legacy_protos: want["legacy_protocols"] = temp_want_legacy_protos commands.extend(self._add_lldp_protocols(want, have)) if updates: for key, value in iteritems(updates): if value: if key == "enable": commands.append(self._compute_command()) elif key == "address": commands.append( self._compute_command( "management-address", str(value) ) ) elif key == "snmp": if value == "disable": commands.append( self._compute_command(key, remove=True) ) else: commands.append( self._compute_command(key, str(value)) ) return commands def _add_lldp_protocols(self, want, have): commands = [] diff_members = get_lst_diff_for_dicts(want, have, "legacy_protocols") for key in diff_members: commands.append(self._compute_command("legacy-protocols", key)) return commands def _update_lldp_protocols(self, want_item, have_item): commands = [] want_protocols = want_item.get("legacy_protocols") or [] have_protocols = have_item.get("legacy_protocols") or [] members_diff = list_diff_have_only(want_protocols, have_protocols) if members_diff: for member in members_diff: commands.append( self._compute_command( "legacy-protocols", member, remove=True ) ) return commands def _compute_command(self, key=None, value=None, remove=False): if remove: cmd = "delete service lldp" else: cmd = "set service lldp" if key: cmd += " " + key if value: cmd += " '" + value + "'" return cmd diff --git a/plugins/module_utils/network/vyos/config/lldp_interfaces/lldp_interfaces.py b/plugins/module_utils/network/vyos/config/lldp_interfaces/lldp_interfaces.py index 94e39c3..f1f299f 100644 --- a/plugins/module_utils/network/vyos/config/lldp_interfaces/lldp_interfaces.py +++ b/plugins/module_utils/network/vyos/config/lldp_interfaces/lldp_interfaces.py @@ -1,459 +1,459 @@ # # -*- 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_lldp_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 ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( ConfigBase, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import ( Facts, ) from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( to_list, dict_diff, ) from ansible.module_utils.six import iteritems from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( search_obj_in_list, search_dict_tv_in_list, key_value_in_dict, is_dict_element_present, ) class Lldp_interfaces(ConfigBase): """ The vyos_lldp_interfaces class """ gather_subset = [ "!all", "!min", ] gather_network_resources = [ "lldp_interfaces", ] params = ["enable", "location", "name"] def __init__(self, module): super(Lldp_interfaces, self).__init__(module) def get_lldp_interfaces_facts(self, data=None): - """ Get the 'facts' (the current configuration) + """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 ) lldp_interfaces_facts = facts["ansible_network_resources"].get( "lldp_interfaces" ) if not lldp_interfaces_facts: return [] return lldp_interfaces_facts def execute_module(self): - """ Execute the module + """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_lldp_interfaces_facts = self.get_lldp_interfaces_facts() else: existing_lldp_interfaces_facts = [] if self.state in self.ACTION_STATES or self.state == "rendered": commands.extend(self.set_config(existing_lldp_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_lldp_interfaces_facts = self.get_lldp_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_lldp_interfaces_facts( data=running_config ) else: changed_lldp_interfaces_facts = [] if self.state in self.ACTION_STATES: result["before"] = existing_lldp_interfaces_facts if result["changed"]: result["after"] = changed_lldp_interfaces_facts elif self.state == "gathered": result["gathered"] = changed_lldp_interfaces_facts result["warnings"] = warnings return result def set_config(self, existing_lldp_interfaces_facts): - """ Collect the configuration from the args passed to the module, + """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_lldp_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 + """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 want ): 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(want=want, have=have)) elif self.state == "deleted": if want: for item in want: name = item["name"] have_item = search_obj_in_list(name, have) commands.extend( self._state_deleted(want=None, have=have_item) ) else: for have_item in have: commands.extend( self._state_deleted(want=None, have=have_item) ) else: for want_item in want: name = want_item["name"] have_item = search_obj_in_list(name, have) if self.state in ("merged", "rendered"): commands.extend( self._state_merged(want=want_item, have=have_item) ) if self.state == "replaced": commands.extend( self._state_replaced(want=want_item, have=have_item) ) return commands def _state_replaced(self, want, have): - """ The command generator when state is replaced + """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 + """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 have_item in have: lldp_name = have_item["name"] lldp_in_want = search_obj_in_list(lldp_name, want) if not lldp_in_want: commands.append( self._compute_command(have_item["name"], remove=True) ) for want_item in want: name = want_item["name"] lldp_in_have = search_obj_in_list(name, have) commands.extend(self._state_replaced(want_item, lldp_in_have)) return commands def _state_merged(self, want, have): - """ The command generator when state is merged + """The command generator when state is merged :rtype: A list :returns: the commands necessary to merge the provided into the current configuration """ commands = [] if have: commands.extend(self._render_updates(want, have)) else: commands.extend(self._render_set_commands(want)) return commands def _state_deleted(self, want, have): - """ The command generator when state is deleted + """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: params = Lldp_interfaces.params for attrib in params: if attrib == "location": commands.extend( self._update_location(have["name"], want, have) ) elif have: commands.append(self._compute_command(have["name"], remove=True)) return commands def _render_updates(self, want, have): commands = [] lldp_name = have["name"] commands.extend(self._configure_status(lldp_name, want, have)) commands.extend(self._add_location(lldp_name, want, have)) return commands def _render_set_commands(self, want): commands = [] have = {} lldp_name = want["name"] params = Lldp_interfaces.params for attrib in params: value = want[attrib] if value: if attrib == "location": commands.extend(self._add_location(lldp_name, want, have)) elif attrib == "enable": if not value: commands.append( self._compute_command(lldp_name, value="disable") ) else: commands.append(self._compute_command(lldp_name)) return commands def _configure_status(self, name, want_item, have_item): commands = [] if is_dict_element_present(have_item, "enable"): temp_have_item = False else: temp_have_item = True if want_item["enable"] != temp_have_item: if want_item["enable"]: commands.append( self._compute_command(name, value="disable", remove=True) ) else: commands.append(self._compute_command(name, value="disable")) return commands def _add_location(self, name, want_item, have_item): commands = [] have_dict = {} have_ca = {} set_cmd = name + " location " want_location_type = want_item.get("location") or {} have_location_type = have_item.get("location") or {} if want_location_type["coordinate_based"]: want_dict = want_location_type.get("coordinate_based") or {} if is_dict_element_present(have_location_type, "coordinate_based"): have_dict = have_location_type.get("coordinate_based") or {} location_type = "coordinate-based" updates = dict_diff(have_dict, want_dict) for key, value in iteritems(updates): if value: commands.append( self._compute_command( set_cmd + location_type, key, str(value) ) ) elif want_location_type["civic_based"]: location_type = "civic-based" want_dict = want_location_type.get("civic_based") or {} want_ca = want_dict.get("ca_info") or [] if is_dict_element_present(have_location_type, "civic_based"): have_dict = have_location_type.get("civic_based") or {} have_ca = have_dict.get("ca_info") or [] if want_dict["country_code"] != have_dict["country_code"]: commands.append( self._compute_command( set_cmd + location_type, "country-code", str(want_dict["country_code"]), ) ) else: commands.append( self._compute_command( set_cmd + location_type, "country-code", str(want_dict["country_code"]), ) ) commands.extend(self._add_civic_address(name, want_ca, have_ca)) elif want_location_type["elin"]: location_type = "elin" if is_dict_element_present(have_location_type, "elin"): if want_location_type.get("elin") != have_location_type.get( "elin" ): commands.append( self._compute_command( set_cmd + location_type, value=str(want_location_type["elin"]), ) ) else: commands.append( self._compute_command( set_cmd + location_type, value=str(want_location_type["elin"]), ) ) return commands def _update_location(self, name, want_item, have_item): commands = [] del_cmd = name + " location" want_location_type = want_item.get("location") or {} have_location_type = have_item.get("location") or {} if want_location_type["coordinate_based"]: want_dict = want_location_type.get("coordinate_based") or {} if is_dict_element_present(have_location_type, "coordinate_based"): have_dict = have_location_type.get("coordinate_based") or {} location_type = "coordinate-based" for key, value in iteritems(have_dict): only_in_have = key_value_in_dict(key, value, want_dict) if not only_in_have: commands.append( self._compute_command( del_cmd + location_type, key, str(value), True ) ) else: commands.append(self._compute_command(del_cmd, remove=True)) elif want_location_type["civic_based"]: want_dict = want_location_type.get("civic_based") or {} want_ca = want_dict.get("ca_info") or [] if is_dict_element_present(have_location_type, "civic_based"): have_dict = have_location_type.get("civic_based") or {} have_ca = have_dict.get("ca_info") commands.extend( self._update_civic_address(name, want_ca, have_ca) ) else: commands.append(self._compute_command(del_cmd, remove=True)) else: if is_dict_element_present(have_location_type, "elin"): if want_location_type.get("elin") != have_location_type.get( "elin" ): commands.append( self._compute_command(del_cmd, remove=True) ) else: commands.append(self._compute_command(del_cmd, remove=True)) return commands def _add_civic_address(self, name, want, have): commands = [] for item in want: ca_type = item["ca_type"] ca_value = item["ca_value"] obj_in_have = search_dict_tv_in_list( ca_type, ca_value, have, "ca_type", "ca_value" ) if not obj_in_have: commands.append( self._compute_command( key=name + " location civic-based ca-type", attrib=str(ca_type) + " ca-value", value=ca_value, ) ) return commands def _update_civic_address(self, name, want, have): commands = [] for item in have: ca_type = item["ca_type"] ca_value = item["ca_value"] in_want = search_dict_tv_in_list( ca_type, ca_value, want, "ca_type", "ca_value" ) if not in_want: commands.append( self._compute_command( name, "location civic-based ca-type", str(ca_type), remove=True, ) ) return commands def _compute_command(self, key, attrib=None, value=None, remove=False): if remove: cmd = "delete service lldp interface " else: cmd = "set service lldp interface " cmd += key if attrib: cmd += " " + attrib if value: cmd += " '" + value + "'" return cmd diff --git a/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py index c0a4c78..059b92d 100644 --- a/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py +++ b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py @@ -1,949 +1,949 @@ # # -*- 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_ospfv2 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, remove_empties, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import ( Facts, ) from ansible.module_utils.six import iteritems from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( list_diff_want_only, _in_target, _is_w_same, _bool_to_str, ) class Ospfv2(ConfigBase): """ The vyos_ospfv2 class """ gather_subset = ["!all", "!min"] gather_network_resources = ["ospfv2"] def __init__(self, module): super(Ospfv2, self).__init__(module) def get_ospfv2_facts(self, data=None): - """ Get the 'facts' (the current configuration) + """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 ) ospfv2_facts = facts["ansible_network_resources"].get("ospfv2", {}) return ospfv2_facts def execute_module(self): - """ Execute the module + """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_ospfv2_facts = self.get_ospfv2_facts() else: existing_ospfv2_facts = {} if self.state in self.ACTION_STATES or self.state == "rendered": commands.extend(self.set_config(existing_ospfv2_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_ospfv2_facts = self.get_ospfv2_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_ospfv2_facts(data=running_config) else: changed_ospfv2_facts = {} if self.state in self.ACTION_STATES: result["before"] = existing_ospfv2_facts if result["changed"]: result["after"] = changed_ospfv2_facts elif self.state == "gathered": result["gathered"] = changed_ospfv2_facts result["warnings"] = warnings return result def set_config(self, existing_ospfv2_facts): - """ Collect the configuration from the args passed to the module, + """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_ospfv2_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 + """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 == "deleted": commands.extend(self._state_deleted(h)) elif self.state in ("merged", "rendered"): commands.extend(self._state_merged(w, h)) elif self.state == "replaced": commands.extend(self._state_replaced(w, h)) return commands def search_obj_in_have(self, have, w_name, key): """ This function returns the rule-set/rule if it is present in target config. :param have: target config. :param w_name: rule-set name. :param type: rule_sets/rule/r_list. :return: rule-set/rule. """ if have: for item in have: if item[key] == w_name[key]: return item return None def _state_replaced(self, want, have): - """ The command generator when state is replaced + """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._render_ospf_param(have, want, opr=False)) commands.extend(self._render_ospf_param(want, have)) return commands def _state_merged(self, want, have): - """ The command generator when state is merged + """The command generator when state is merged :rtype: A list :returns: the commands necessary to merge the provided into the current configuration """ commands = [] commands.extend(self._render_ospf_param(want, have)) return commands def _state_deleted(self, have): - """ The command generator when state is deleted + """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 have: commands.append("delete protocols ospf") return commands def _render_ospf_param(self, want, have, opr=True): """ This function forms the set/delete commands for ospf leaf attributes and triggers the process for other child attributes. for firewall_global attributes. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated commands list. """ commands = [] w = deepcopy(remove_empties(want)) leaf = ("default_metric", "log_adjacency_changes") if w: for (key, val) in iteritems(w): if opr and key in leaf and not _is_w_same(w, have, key): commands.append( self._form_attr_cmd( attr=key, val=_bool_to_str(val), opr=opr ) ) elif not opr and key in leaf and not _in_target(have, key): commands.append( self._form_attr_cmd( attr=key, val=_bool_to_str(val), opr=opr ) ) else: commands.extend( self._render_child_param(w, have, key, opr) ) return commands def _render_child_param(self, w, h, key, opr=True): """ This function invoke the function to extend commands based on the key. :param w: the desired configuration. :param h: the current configuration. :param key: attribute name. :param opr: operation. :return: list of commands. """ commands = [] if key in ("neighbor", "redistribute"): commands.extend(self._render_list_dict_param(key, w, h, opr=opr)) elif key in ("default_information", "max_metric"): commands.extend(self._render_nested_dict_param(key, w, h, opr=opr)) elif key in ("mpls_te", "auto_cost", "parameters", "auto_cost"): commands.extend(self._render_dict_param(key, w, h, opr=opr)) elif key in ( "route_map", "passive_interface", "passive_interface_exclude", ): commands.extend(self._render_list_param(key, w, h, opr=opr)) elif key == "areas": commands.extend(self._render_areas(key, w, h, opr=opr)) elif key == "timers": commands.extend(self._render_timers(key, w, h, opr=opr)) elif key == "distance": commands.extend(self._render_distance(key, w, h, opr=opr)) return commands def _render_dict_param(self, attr, want, have, opr=True): """ This function generate the commands for dictionary elements. :param attr: attribute name. :param w: the desired configuration. :param h: the target config. :param opr: True/False. :return: generated list of commands. """ commands = [] h = {} if have: h = have.get(attr) or {} if not opr and not h: commands.append(self._form_attr_cmd(attr=attr, opr=opr)) elif want[attr]: leaf_dict = { "auto_cost": "reference_bandwidth", "mpls_te": ("enabled", "router_address"), "parameters": ( "router_id", "abr_type", "opaque_lsa", "rfc1583_compatibility", ), } leaf = leaf_dict[attr] for (item, value) in iteritems(want[attr]): if ( opr and item in leaf and not _is_w_same(want[attr], h, item) ): if item == "enabled": item = "enable" if item in ( "opaque_lsa", "enable", "rfc1583_compatibility", ): commands.append( self._form_attr_cmd(key=attr, attr=item, opr=opr) ) else: commands.append( self._form_attr_cmd( key=attr, attr=item, val=value, opr=opr ) ) elif not opr and item in leaf and not _in_target(h, item): if item == "enabled": commands.append( self._form_attr_cmd( key=attr, attr="enable", opr=opr ) ) else: commands.append( self._form_attr_cmd(key=attr, attr=item, opr=opr) ) return commands def _render_list_param(self, attr, want, have, cmd=None, opr=True): """ This function forms the commands for passed target list attributes'. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param cmd: commands to be prepend. :param opr: True/False. :return: generated list of commands. """ commands = [] h = [] if want: w = want.get(attr) or [] if have: h = have.get(attr) or [] if not cmd: cmd = self._compute_command(opr=opr) if w: if opr: members = list_diff_want_only(w, h) for member in members: command = cmd + attr.replace("_", "-") + " " if attr == "network": command += member["address"] else: command += member commands.append(command) elif not opr: if h: for member in w: if attr == "network": if not self.search_obj_in_have( h, member, "address" ): commands.append( cmd + attr.replace("_", "-") + " " + member["address"] ) elif member not in h: commands.append( cmd + attr.replace("_", "-") + " " + member ) else: commands.append(cmd + " " + attr.replace("_", "-")) return commands def _render_vlink(self, attr, want, have, cmd=None, opr=True): """ This function forms the set/delete commands based on the 'opr' type for attributes with in desired list of dictionary. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param cmd: commands to be prepend. :param opr: True/False. :return: generated commands list. """ commands = [] h = [] name = {"virtual_link": "address"} leaf_dict = { "virtual_link": ( "address", "dead_interval", "transmit_delay", "hello_interval", "retransmit_interval", ) } leaf = leaf_dict[attr] w = want.get(attr) or [] if have: h = have.get(attr) or [] if not opr and not h: commands.append(cmd + attr.replace("_", "-")) elif w: for w_item in w: for (key, val) in iteritems(w_item): if not cmd: cmd = self._compute_command(opr=opr) h_item = self.search_obj_in_have(h, w_item, name[attr]) if ( opr and key in leaf and not _is_w_same(w_item, h_item, key) ): if key in "address": commands.append( cmd + attr.replace("_", "-") + " " + str(val) ) else: commands.append( cmd + attr.replace("_", "-") + " " + w_item[name[attr]] + " " + key.replace("_", "-") + " " + str(val) ) elif ( not opr and key in leaf and not _in_target(h_item, key) ): if key in "address": commands.append( cmd + attr.replace("_", "-") + " " + str(val) ) else: commands.append( cmd + attr.replace("_", "-") + " " + w_item[name[attr]] + " " + key ) elif key == "authentication": commands.extend( self._render_vlink_auth( attr, key, w_item, h_item, w_item["address"], cmd, opr, ) ) return commands def _render_vlink_auth( self, attr, key, want, have, address, cmd=None, opr=True ): """ This function forms the set/delete commands based on the 'opr' type for attributes with in desired list of dictionary. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param cmd: commands to be prepend. :param opr: True/False. :return: generated commands list. """ commands = [] h = [] w = want.get(key) or {} if have: h = have.get(key) or {} cmd += attr.replace("_", "-") + " " + address + " " + key + " " commands.extend(self._render_list_dict_param("md5", w, h, cmd, opr)) return commands def _render_list_dict_param(self, attr, want, have, cmd=None, opr=True): """ This function forms the set/delete commands based on the 'opr' type for attributes with in desired list of dictionary. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param cmd: commands to be prepend. :param opr: True/False. :return: generated commands list. """ commands = [] h = [] name = { "redistribute": "route_type", "neighbor": "neighbor_id", "range": "address", "md5": "key_id", "vlink": "address", } leaf_dict = { "md5": "md5_key", "redistribute": ( "metric", "route_map", "route_type", "metric_type", ), "neighbor": ("priority", "poll_interval", "neighbor_id"), "range": ("cost", "address", "substitute", "not_advertise"), "vlink": ( "address", "dead_interval", "transmit_delay", "hello_interval", "retransmit_interval", ), } leaf = leaf_dict[attr] w = want.get(attr) or [] if have: h = have.get(attr) or [] if not opr and not h: commands.append(self._compute_command(attr=attr, opr=opr)) elif w: for w_item in w: for (key, val) in iteritems(w_item): if not cmd: cmd = self._compute_command(opr=opr) h_item = self.search_obj_in_have(h, w_item, name[attr]) if ( opr and key in leaf and not _is_w_same(w_item, h_item, key) ): if key in ( "route_type", "neighbor_id", "address", "key_id", ): commands.append(cmd + attr + " " + str(val)) elif key == "cost": commands.append( cmd + attr + " " + w_item[name[attr]] + " " + key + " " + str(val) ) elif key == "not_advertise": commands.append( cmd + attr + " " + w_item[name[attr]] + " " + key.replace("_", "-") ) elif key == "md5_key": commands.append( cmd + attr + " " + "key-id" + " " + str(w_item[name[attr]]) + " " + key.replace("_", "-") + " " + w_item[key] ) else: commands.append( cmd + attr + " " + w_item[name[attr]] + " " + key.replace("_", "-") + " " + str(val) ) elif ( not opr and key in leaf and not _in_target(h_item, key) ): if key in ( "route_type", "neighbor_id", "address", "key_id", ): commands.append(cmd + attr + " " + str(val)) else: commands.append( cmd + attr + " " + w_item[name[attr]] + " " + key ) return commands def _render_nested_dict_param(self, attr, want, have, opr=True): """ This function forms the set/delete commands based on the 'opr' type for attributes with in desired nested dicts. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param cmd: commands to be prepend. :param opr: True/False. :return: generated commands list. """ commands = [] attr_dict = { "default_information": "originate", "max_metric": "router_lsa", } leaf_dict = { "default_information": ( "always", "metric", "metric_type", "route_map", ), "max_metric": ("administrative", "on_startup", "on_shutdown"), } h = {} w = want.get(attr) or {} if have: h = have.get(attr) or {} if not opr and not h: commands.append(self._form_attr_cmd(attr=attr, opr=opr)) elif w: key = attr_dict[attr] w_attrib = want[attr].get(key) or {} cmd = self._compute_command(opr=opr) h_attrib = {} if w_attrib: leaf = leaf_dict[attr] if h and key in h.keys(): h_attrib = h.get(key) or {} for (item, val) in iteritems(w[key]): if ( opr and item in leaf and not _is_w_same(w[key], h_attrib, item) ): if item in ("administrative", "always") and val: commands.append( cmd + attr.replace("_", "-") + " " + key.replace("_", "-") + " " + item.replace("_", "-") ) elif item not in ("administrative", "always"): commands.append( cmd + attr.replace("_", "-") + " " + key.replace("_", "-") + " " + item.replace("_", "-") + " " + str(val) ) elif ( not opr and item in leaf and not _in_target(h_attrib, item) ): commands.append(cmd + attr + " " + item) return commands def _render_areas(self, attr, want, have, opr=True): """ This function forms the set/delete commands based on the 'opr' type for ospf area attributes. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated commands list. """ commands = [] h_lst = {} w_lst = want.get(attr) or [] l_set = ("area_id", "shortcut", "authentication") if have: h_lst = have.get(attr) or [] if not opr and not h_lst: commands.append(self._form_attr_cmd(attr="area", opr=opr)) elif w_lst: for w_area in w_lst: cmd = ( self._compute_command( key="area", attr=_bool_to_str(w_area["area_id"]), opr=opr, ) + " " ) h_area = self.search_obj_in_have(h_lst, w_area, "area_id") if not opr and not h_area: commands.append( self._form_attr_cmd( key="area", attr=w_area["area_id"], opr=opr ) ) else: for (key, val) in iteritems(w_area): if ( opr and key in l_set and not _is_w_same(w_area, h_area, key) ): if key == "area_id": commands.append( self._form_attr_cmd( attr="area", val=_bool_to_str(val), opr=opr, ) ) else: commands.append( cmd + key + " " + _bool_to_str(val).replace("_", "-") ) elif not opr and key in l_set: if key == "area_id" and not _in_target( h_area, key ): commands.append(cmd) continue if key != "area_id" and not _in_target( h_area, key ): commands.append(cmd + val + " " + key) elif key == "area_type": commands.extend( self._render_area_type( w_area, h_area, key, cmd, opr ) ) elif key == "network": commands.extend( self._render_list_param( key, w_area, h_area, cmd, opr ) ) elif key == "range": commands.extend( self._render_list_dict_param( key, w_area, h_area, cmd, opr ) ) elif key == "virtual_link": commands.extend( self._render_vlink( key, w_area, h_area, cmd, opr ) ) return commands def _render_area_type(self, want, have, attr, cmd, opr=True): """ This function forms the set/delete commands based on the 'opr' type for area_types attributes. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param cmd: command to prepend. :param opr: True/False. :return: generated commands list. """ commands = [] h_type = {} w_type = want.get(attr) or [] if have: h_type = have.get(attr) or {} if not opr and not h_type: commands.append(cmd + attr.replace("_", "-")) elif w_type: key = "normal" if ( opr and key in w_type.keys() and not _is_w_same(w_type, h_type, key) ): if not w_type[key] and h_type and h_type[key]: commands.append( cmd.replace("set", "delete") + attr.replace("_", "-") + " " + key ) elif w_type[key]: commands.append(cmd + attr.replace("_", "-") + " " + key) elif ( not opr and key in w_type.keys() and not (h_type and key in h_type.keys()) ): commands.append( cmd + want["area"] + " " + attr.replace("_", "-") ) a_type = { "nssa": ("set", "default_cost", "no_summary", "translate"), "stub": ("set", "default_cost", "no_summary"), } for key in a_type: w_area = want[attr].get(key) or {} h_area = {} if w_area: if h_type and key in h_type.keys(): h_area = h_type.get(key) or {} for (item, val) in iteritems(w_type[key]): if ( opr and item in a_type[key] and not _is_w_same(w_type[key], h_area, item) ): if item == "set" and val: commands.append( cmd + attr.replace("_", "-") + " " + key ) elif not val and h_area and h_area[item]: commands.append( cmd.replace("set", "delete") + attr.replace("_", "-") + " " + key ) elif item != "set": commands.append( cmd + attr.replace("_", "-") + " " + key + " " + item.replace("_", "-") + " " + str(val) ) elif ( not opr and item in a_type[key] and not (h_type and key in h_type) ): if item == "set": commands.append( cmd + attr.replace("_", "-") + " " + key ) else: commands.append( cmd + want["area"] + " " + attr.replace("_", "-") + " " + key + " " + item.replace("_", "-") ) return commands def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True): """ This function forms the command for leaf attribute. :param key: parent key. :param attr: attribute name :param value: value :param opr: True/False. :return: generated command. """ return self._compute_command( key, attr=self._map_attrib(attr), val=val, opr=opr ) def _compute_command( self, key=None, attr=None, val=None, remove=False, opr=True ): """ This function construct the add/delete command based on passed attributes. :param key: parent key. :param attr: attribute name :param value: value :param opr: True/False. :return: generated command. """ if remove or not opr: cmd = "delete protocols ospf " else: cmd = "set protocols ospf " if key: cmd += key.replace("_", "-") + " " if attr: cmd += attr.replace("_", "-") if val: cmd += " '" + str(val) + "'" return cmd def _map_attrib(self, attrib): """ - This function construct the regex string. - replace the underscore with hyphen. :param attrib: attribute :return: regex string """ return "disable" if attrib == "disabled" else attrib.replace("_", "-") diff --git a/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py index 09b9200..c43dfe7 100644 --- a/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py +++ b/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py @@ -1,464 +1,464 @@ # # -*- 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_ospfv3 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, remove_empties, search_obj_in_list, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import ( Facts, ) from ansible.module_utils.six import iteritems from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( _in_target, _is_w_same, _bool_to_str, ) class Ospfv3(ConfigBase): """ The vyos_ospfv3 class """ gather_subset = [ "!all", "!min", ] gather_network_resources = [ "ospfv3", ] def __init__(self, module): super(Ospfv3, self).__init__(module) def get_ospfv3_facts(self, data=None): - """ Get the 'facts' (the current configuration) + """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 ) ospfv3_facts = facts["ansible_network_resources"].get("ospfv3", {}) return ospfv3_facts def execute_module(self): - """ Execute the module + """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_ospfv3_facts = self.get_ospfv3_facts() else: existing_ospfv3_facts = {} if self.state in self.ACTION_STATES or self.state == "rendered": commands.extend(self.set_config(existing_ospfv3_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_ospfv3_facts = self.get_ospfv3_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_ospfv3_facts(data=running_config) else: changed_ospfv3_facts = {} if self.state in self.ACTION_STATES: result["before"] = existing_ospfv3_facts if result["changed"]: result["after"] = changed_ospfv3_facts elif self.state == "gathered": result["gathered"] = changed_ospfv3_facts result["warnings"] = warnings return result def set_config(self, existing_ospfv3_facts): - """ Collect the configuration from the args passed to the module, + """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_ospfv3_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 + """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 == "deleted": commands.extend(self._state_deleted(w, h)) elif self.state in ("merged", "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 + """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._render_ospf_param(have, want, opr=False)) commands.extend(self._render_ospf_param(want, have)) return commands def _state_merged(self, want, have): - """ The command generator when state is merged + """The command generator when state is merged :rtype: A list :returns: the commands necessary to merge the provided into the current configuration """ commands = [] commands.extend(self._render_ospf_param(want, have)) return commands def _state_deleted(self, want, have): - """ The command generator when state is deleted + """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 have: commands.append("delete protocols ospfv3") return commands def _render_ospf_param(self, want, have, opr=True): """ This function forms the set/delete commands for ospf leaf attributes and triggers the process for other child attributes. for firewall_global attributes. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated commands list. """ commands = [] w = deepcopy(remove_empties(want)) if w: for key, val in iteritems(w): commands.extend(self._render_child_param(w, have, key, opr)) return commands def _render_child_param(self, w, h, key, opr=True): """ This function invoke the function to extend commands based on the key. :param w: the desired configuration. :param h: the current configuration. :param key: attribute name. :param opr: operation. :return: list of commands. """ commands = [] if key == "areas": commands.extend(self._render_areas(key, w, h, opr=opr)) elif key == "parameters": commands.extend(self._render_dict_param(key, w, h, opr=opr)) elif key == "redistribute": commands.extend(self._render_list_dict_param(key, w, h, opr=opr)) return commands def _render_dict_param(self, attr, want, have, opr=True): """ This function generate the commands for dictionary elements. :param attr: attribute name. :param w: the desired configuration. :param h: the target config. :param opr: True/False. :return: generated list of commands. """ commands = [] h = {} if have: h = have.get(attr) or {} if not opr and not h: commands.append(self._form_attr_cmd(attr=attr, opr=opr)) elif want[attr]: leaf_dict = {"parameters": "router_id"} leaf = leaf_dict[attr] for item, value in iteritems(want[attr]): if ( opr and item in leaf and not _is_w_same(want[attr], h, item) ): commands.append( self._form_attr_cmd( key=attr, attr=item, val=value, opr=opr ) ) elif not opr and item in leaf and not _in_target(h, item): commands.append( self._form_attr_cmd(key=attr, attr=item, opr=opr) ) return commands def _render_list_dict_param(self, attr, want, have, cmd=None, opr=True): """ This function forms the set/delete commands based on the 'opr' type for attributes with in desired list of dictionary. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param cmd: commands to be prepend. :param opr: True/False. :return: generated commands list. """ commands = [] h = [] name = { "redistribute": "route_type", "range": "address", } leaf_dict = { "redistribute": ("route_map", "route_type"), "range": ("address", "advertise", "not_advertise"), } leaf = leaf_dict[attr] w = want.get(attr) or [] if have: h = have.get(attr) or [] if not opr and not h: commands.append(self._compute_command(attr=attr, opr=opr)) elif w: for w_item in w: for key, val in iteritems(w_item): if not cmd: cmd = self._compute_command(opr=opr) h_item = search_obj_in_list( w_item[name[attr]], h, name[attr] ) if ( opr and key in leaf and not _is_w_same(w_item, h_item, key) ): if key == "route_type" or ( key == "address" and "advertise" not in w_item and "not-advertise" not in w_item ): if not val: cmd = cmd.replace("set", "delete") commands.append(cmd + attr + " " + str(val)) elif key in leaf_dict["range"] and key != "address": commands.append( cmd + attr + " " + w_item[name[attr]] + " " + key.replace("_", "-") ) elif key == "route_map": commands.append( cmd + attr + " " + w_item[name[attr]] + " " + key.replace("_", "-") + " " + str(val) ) elif ( not opr and key in leaf and not _in_target(h_item, key) ): if key in ("route_type", "address"): commands.append(cmd + attr + " " + str(val)) else: commands.append( cmd + (attr + " " + w_item[name[attr]] + " " + key) ) return commands def _render_areas(self, attr, want, have, opr=True): """ This function forms the set/delete commands based on the 'opr' type for ospf area attributes. :param attr: attribute name. :param w: the desired config. :param h: the target config. :param opr: True/False. :return: generated commands list. """ commands = [] h_lst = {} w_lst = want.get(attr) or [] l_set = ("area_id", "export_list", "import_list") if have: h_lst = have.get(attr) or [] if not opr and not h_lst: commands.append(self._form_attr_cmd(attr="area", opr=opr)) elif w_lst: for w_area in w_lst: cmd = ( self._compute_command( key="area", attr=_bool_to_str(w_area["area_id"]), opr=opr, ) + " " ) h_area = search_obj_in_list( w_area["area_id"], h_lst, "area_id" ) if not opr and not h_area: commands.append( self._form_attr_cmd( key="area", attr=w_area["area_id"], opr=opr ) ) else: for key, val in iteritems(w_area): if ( opr and key in l_set and not _is_w_same(w_area, h_area, key) ): if key == "area_id": commands.append( self._form_attr_cmd( attr="area", val=_bool_to_str(val), opr=opr, ) ) else: commands.append( cmd + key.replace("_", "-") + " " + _bool_to_str(val).replace("_", "-") ) elif not opr and key in l_set: if key == "area_id" and not _in_target( h_area, key ): commands.append(cmd) continue if key != "area_id" and not _in_target( h_area, key ): commands.append(cmd + val + " " + key) elif key == "range": commands.extend( self._render_list_dict_param( key, w_area, h_area, cmd, opr ) ) return commands def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True): """ This function forms the command for leaf attribute. :param key: parent key. :param attr: attribute name :param value: value :param opr: True/False. :return: generated command. """ return self._compute_command( key, attr=self._map_attrib(attr), val=val, opr=opr ) def _compute_command( self, key=None, attr=None, val=None, remove=False, opr=True ): """ This function construct the add/delete command based on passed attributes. :param key: parent key. :param attr: attribute name :param value: value :param opr: True/False. :return: generated command. """ if remove or not opr: cmd = "delete protocols ospfv3 " else: cmd = "set protocols ospfv3 " if key: cmd += key.replace("_", "-") + " " if attr: cmd += attr.replace("_", "-") if val and opr: cmd += " '" + str(val) + "'" return cmd def _map_attrib(self, attrib): """ - This function construct the regex string. - replace the underscore with hyphen. :param attrib: attribute :return: regex string """ return "disable" if attrib == "disabled" else attrib.replace("_", "-") diff --git a/plugins/module_utils/network/vyos/config/static_routes/static_routes.py b/plugins/module_utils/network/vyos/config/static_routes/static_routes.py index b359dbb..f1d4f38 100644 --- a/plugins/module_utils/network/vyos/config/static_routes/static_routes.py +++ b/plugins/module_utils/network/vyos/config/static_routes/static_routes.py @@ -1,621 +1,621 @@ # # -*- 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_static_routes 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, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import ( Facts, ) from ansible.module_utils.six import iteritems from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( get_route_type, get_lst_diff_for_dicts, get_lst_same_for_dicts, dict_delete, ) class Static_routes(ConfigBase): """ The vyos_static_routes class """ gather_subset = [ "!all", "!min", ] gather_network_resources = [ "static_routes", ] def __init__(self, module): super(Static_routes, self).__init__(module) def get_static_routes_facts(self, data=None): - """ Get the 'facts' (the current configuration) + """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 ) static_routes_facts = facts["ansible_network_resources"].get( "static_routes" ) if not static_routes_facts: return [] return static_routes_facts def execute_module(self): - """ Execute the module + """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_static_routes_facts = self.get_static_routes_facts() else: existing_static_routes_facts = [] if self.state in self.ACTION_STATES or self.state == "rendered": commands.extend(self.set_config(existing_static_routes_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_static_routes_facts = self.get_static_routes_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_static_routes_facts( data=running_config ) else: changed_static_routes_facts = [] if self.state in self.ACTION_STATES: result["before"] = existing_static_routes_facts if result["changed"]: result["after"] = changed_static_routes_facts elif self.state == "gathered": result["gathered"] = changed_static_routes_facts result["warnings"] = warnings return result def set_config(self, existing_static_routes_facts): - """ Collect the configuration from the args passed to the module, + """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_static_routes_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 + """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 want ): 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(want=want, have=have)) elif self.state == "deleted": commands.extend(self._state_deleted(want=want, have=have)) elif want: routes = self._get_routes(want) for r in routes: h_item = self.search_route_in_have(have, r["dest"]) if self.state in ("merged", "rendered"): commands.extend(self._state_merged(want=r, have=h_item)) elif self.state == "replaced": commands.extend(self._state_replaced(want=r, have=h_item)) return commands def search_route_in_have(self, have, want_dest): """ This function returns the route if its found in have config. :param have: :param dest: :return: the matched route """ routes = self._get_routes(have) for r in routes: if r["dest"] == want_dest: return r return None def _state_replaced(self, want, have): - """ The command generator when state is replaced + """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 key, value in iteritems(want): if value: if key == "next_hops": commands.extend(self._update_next_hop(want, have)) elif key == "blackhole_config": commands.extend( self._update_blackhole(key, want, have) ) commands.extend(self._state_merged(want, have)) return commands def _state_overridden(self, want, have): - """ The command generator when state is overridden + """The command generator when state is overridden :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ commands = [] routes = self._get_routes(have) for r in routes: route_in_want = self.search_route_in_have(want, r["dest"]) if not route_in_want: commands.append(self._compute_command(r["dest"], remove=True)) routes = self._get_routes(want) for r in routes: route_in_have = self.search_route_in_have(have, r["dest"]) commands.extend(self._state_replaced(r, route_in_have)) return commands def _state_merged(self, want, have, opr=True): - """ The command generator when state is merged + """The command generator when state is merged :rtype: A list :returns: the commands necessary to merge the provided into the current configuration """ commands = [] if have: commands.extend(self._render_updates(want, have)) else: commands.extend(self._render_set_commands(want)) return commands def _state_deleted(self, want, have): - """ The command generator when state is deleted + """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: routes = self._get_routes(want) if not routes: for w in want: af = w["address_families"] for item in af: if self.afi_in_have(have, item): commands.append( self._compute_command( afi=item["afi"], remove=True ) ) else: routes = self._get_routes(have) if self._is_ip_route_exist(routes): commands.append(self._compute_command(afi="ipv4", remove=True)) if self._is_ip_route_exist(routes, "route6"): commands.append(self._compute_command(afi="ipv6", remove=True)) return commands def _render_set_commands(self, want): """ This function returns the list of commands to add attributes which are present in want :param want: :return: list of commands. """ commands = [] have = {} for key, value in iteritems(want): if value: if key == "dest": commands.append(self._compute_command(dest=want["dest"])) elif key == "blackhole_config": commands.extend(self._add_blackhole(key, want, have)) elif key == "next_hops": commands.extend(self._add_next_hop(want, have)) return commands def _add_blackhole(self, key, want, have): """ This function gets the diff for blackhole config specific attributes and form the commands for attributes which are present in want but not in have. :param key: :param want: :param have: :return: list of commands """ commands = [] want_copy = deepcopy(remove_empties(want)) have_copy = deepcopy(remove_empties(have)) want_blackhole = want_copy.get(key) or {} have_blackhole = have_copy.get(key) or {} updates = dict_delete(want_blackhole, have_blackhole) if updates: for attrib, value in iteritems(updates): if value: if attrib == "distance": commands.append( self._compute_command( dest=want["dest"], key="blackhole", attrib=attrib, remove=False, value=str(value), ) ) elif attrib == "type": commands.append( self._compute_command( dest=want["dest"], key="blackhole" ) ) return commands def _add_next_hop(self, want, have, opr=True): """ This function gets the diff for next hop specific attributes and form the commands to add attributes which are present in want but not in have. :param want: :param have: :return: list of commands. """ commands = [] want_copy = deepcopy(remove_empties(want)) have_copy = deepcopy(remove_empties(have)) if not opr: diff_next_hops = get_lst_same_for_dicts( want_copy, have_copy, "next_hops" ) else: diff_next_hops = get_lst_diff_for_dicts( want_copy, have_copy, "next_hops" ) if diff_next_hops: for hop in diff_next_hops: for element in hop: if element == "forward_router_address": commands.append( self._compute_command( dest=want["dest"], key="next-hop", value=hop[element], opr=opr, ) ) elif element == "enabled" and not hop[element]: commands.append( self._compute_command( dest=want["dest"], key="next-hop", attrib=hop["forward_router_address"], value="disable", opr=opr, ) ) elif element == "admin_distance": commands.append( self._compute_command( dest=want["dest"], key="next-hop", attrib=hop["forward_router_address"] + " " + element, value=str(hop[element]), opr=opr, ) ) elif element == "interface": commands.append( self._compute_command( dest=want["dest"], key="next-hop", attrib=hop["forward_router_address"] + " " + element, value=hop[element], opr=opr, ) ) return commands def _update_blackhole(self, key, want, have): """ This function gets the difference for blackhole dict and form the commands to delete the attributes which are present in have but not in want. :param want: :param have: :return: list of commands :param key: :param want: :param have: :return: list of commands """ commands = [] want_copy = deepcopy(remove_empties(want)) have_copy = deepcopy(remove_empties(have)) want_blackhole = want_copy.get(key) or {} have_blackhole = have_copy.get(key) or {} updates = dict_delete(have_blackhole, want_blackhole) if updates: for attrib, value in iteritems(updates): if value: if attrib == "distance": commands.append( self._compute_command( dest=want["dest"], key="blackhole", attrib=attrib, remove=True, value=str(value), ) ) elif ( attrib == "type" and "distance" not in want_blackhole.keys() ): commands.append( self._compute_command( dest=want["dest"], key="blackhole", remove=True ) ) return commands def _update_next_hop(self, want, have, opr=True): """ This function gets the difference for next_hops list and form the commands to delete the attributes which are present in have but not in want. :param want: :param have: :return: list of commands """ commands = [] want_copy = deepcopy(remove_empties(want)) have_copy = deepcopy(remove_empties(have)) diff_next_hops = get_lst_diff_for_dicts( have_copy, want_copy, "next_hops" ) if diff_next_hops: for hop in diff_next_hops: for element in hop: if element == "forward_router_address": commands.append( self._compute_command( dest=want["dest"], key="next-hop", value=hop[element], remove=True, ) ) elif element == "enabled": commands.append( self._compute_command( dest=want["dest"], key="next-hop", attrib=hop["forward_router_address"], value="disable", remove=True, ) ) elif element == "admin_distance": commands.append( self._compute_command( dest=want["dest"], key="next-hop", attrib=hop["forward_router_address"] + " " + element, value=str(hop[element]), remove=True, ) ) elif element == "interface": commands.append( self._compute_command( dest=want["dest"], key="next-hop", attrib=hop["forward_router_address"] + " " + element, value=hop[element], remove=True, ) ) return commands def _render_updates(self, want, have, opr=True): """ This function takes the diff between want and have and invokes the appropriate functions to create the commands to update the attributes. :param want: :param have: :return: list of commands """ commands = [] want_nh = want.get("next_hops") or [] # delete static route operation per destination if not opr and not want_nh: commands.append( self._compute_command(dest=want["dest"], remove=True) ) else: temp_have_next_hops = have.pop("next_hops", None) temp_want_next_hops = want.pop("next_hops", None) updates = dict_diff(have, want) if temp_have_next_hops: have["next_hops"] = temp_have_next_hops if temp_want_next_hops: want["next_hops"] = temp_want_next_hops commands.extend(self._add_next_hop(want, have, opr=opr)) if opr and updates: for key, value in iteritems(updates): if value: if key == "blackhole_config": commands.extend( self._add_blackhole(key, want, have) ) return commands def _compute_command( self, dest=None, key=None, attrib=None, value=None, remove=False, afi=None, opr=True, ): """ This functions construct the required command based on the passed arguments. :param dest: :param key: :param attrib: :param value: :param remove: :return: constructed command """ if remove or not opr: cmd = "delete protocols static " + self.get_route_type(dest, afi) else: cmd = "set protocols static " + self.get_route_type(dest, afi) if dest: cmd += " " + dest if key: cmd += " " + key if attrib: cmd += " " + attrib if value: cmd += " '" + value + "'" return cmd def afi_in_have(self, have, w_item): """ This functions checks for the afi list in have :param have: :param w_item: :return: """ if have: for h in have: af = h.get("address_families") or [] for item in af: if w_item["afi"] == item["afi"]: return True return False def get_route_type(self, dest=None, afi=None): """ This function returns the route type based on destination ip address or afi :param address: :return: """ if dest: return get_route_type(dest) elif afi == "ipv4": return "route" elif afi == "ipv6": return "route6" def _is_ip_route_exist(self, routes, type="route"): """ This functions checks for the type of route. :param routes: :param type: :return: True/False """ for r in routes: if type == self.get_route_type(r["dest"]): return True return False def _get_routes(self, lst): """ This function returns the list of routes :param lst: list of address families :return: list of routes """ r_list = [] for item in lst: af = item["address_families"] for element in af: routes = element.get("routes") or [] for r in routes: r_list.append(r) return r_list diff --git a/plugins/module_utils/network/vyos/facts/facts.py b/plugins/module_utils/network/vyos/facts/facts.py index 4c7b340..caea5fe 100644 --- a/plugins/module_utils/network/vyos/facts/facts.py +++ b/plugins/module_utils/network/vyos/facts/facts.py @@ -1,100 +1,99 @@ # 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.ansible.netcommon.plugins.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.lag_interfaces.lag_interfaces import ( Lag_interfacesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lldp_global.lldp_global import ( Lldp_globalFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lldp_interfaces.lldp_interfaces import ( Lldp_interfacesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_rules.firewall_rules import ( Firewall_rulesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.static_routes.static_routes import ( Static_routesFacts, ) 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.ospfv3.ospfv3 import ( Ospfv3Facts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv2.ospfv2 import ( Ospfv2Facts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.legacy.base import ( Default, Neighbors, Config, ) FACT_LEGACY_SUBSETS = dict(default=Default, neighbors=Neighbors, config=Config) FACT_RESOURCE_SUBSETS = dict( interfaces=InterfacesFacts, l3_interfaces=L3_interfacesFacts, lag_interfaces=Lag_interfacesFacts, lldp_global=Lldp_globalFacts, lldp_interfaces=Lldp_interfacesFacts, static_routes=Static_routesFacts, firewall_rules=Firewall_rulesFacts, firewall_global=Firewall_globalFacts, firewall_interfaces=Firewall_interfacesFacts, ospfv3=Ospfv3Facts, ospfv2=Ospfv2Facts, ) class Facts(FactsBase): - """ The fact class for vyos - """ + """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 + """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 """ if self.VALID_RESOURCE_SUBSETS: self.get_network_resources_facts( 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/firewall_global/firewall_global.py b/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py index 0823259..e6e42d7 100644 --- a/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py +++ b/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py @@ -1,388 +1,387 @@ # # -*- 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_global 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 copy import deepcopy from re import findall, search, M from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( utils, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.firewall_global.firewall_global import ( Firewall_globalArgs, ) class Firewall_globalFacts(object): - """ The vyos firewall_global fact class - """ + """The vyos firewall_global fact class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = Firewall_globalArgs.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_global + """Populate the facts for firewall_global :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 = {} firewalls = findall(r"^set firewall .*$", data, M) if firewalls: objs = self.render_config(firewalls) facts = {} params = utils.validate_config(self.argument_spec, {"config": objs}) facts["firewall_global"] = utils.remove_empties(params["config"]) 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 """ conf = "\n".join( filter( lambda x: ("firewall ipv6-name" and "firewall name" not in x), conf, ) ) a_lst = [ "config_trap", "validation", "log_martians", "syn_cookies", "twa_hazards_protection", ] firewall = self.parse_attr(conf, a_lst) f_sub = { "ping": self.parse_ping(conf), "group": self.parse_group(conf), "route_redirects": self.route_redirects(conf), "state_policy": self.parse_state_policy(conf), } firewall.update(f_sub) return firewall def route_redirects(self, conf): """ This function forms the regex to fetch the afi and invoke functions to fetch route redirects and source routes :param conf: configuration data. :return: generated rule list configuration. """ rr_lst = [] v6_attr = findall( r"^set firewall (?:ipv6-src-route|ipv6-receive-redirects) (\S+)", conf, M, ) if v6_attr: obj = self.parse_rr_attrib(conf, "ipv6") if obj: rr_lst.append(obj) v4_attr = findall( r"^set firewall (?:ip-src-route|receive-redirects|send-redirects) (\S+)", conf, M, ) if v4_attr: obj = self.parse_rr_attrib(conf, "ipv4") if obj: rr_lst.append(obj) return rr_lst def parse_rr_attrib(self, conf, attrib=None): """ This function fetches the 'ip_src_route' invoke function to parse icmp redirects. :param conf: configuration to be parsed. :param attrib: 'ipv4/ipv6'. :return: generated config dictionary. """ cfg_dict = self.parse_attr(conf, ["ip_src_route"], type=attrib) cfg_dict["icmp_redirects"] = self.parse_icmp_redirects(conf, attrib) cfg_dict["afi"] = attrib return cfg_dict def parse_icmp_redirects(self, conf, attrib=None): """ This function triggers the parsing of 'icmp_redirects' attributes. :param conf: configuration to be parsed. :param attrib: 'ipv4/ipv6'. :return: generated config dictionary. """ a_lst = ["send", "receive"] cfg_dict = self.parse_attr(conf, a_lst, type=attrib) return cfg_dict def parse_ping(self, conf): """ This function triggers the parsing of 'ping' attributes. :param conf: configuration to be parsed. :return: generated config dictionary. """ a_lst = ["all", "broadcast"] cfg_dict = self.parse_attr(conf, a_lst) return cfg_dict def parse_state_policy(self, conf): """ This function fetched the connecton type and invoke function to parse other state-policy attributes. :param conf: configuration data. :return: generated rule list configuration. """ sp_lst = [] attrib = "state-policy" policies = findall(r"^set firewall " + attrib + " (\\S+)", conf, M) if policies: rules_lst = [] for sp in set(policies): sp_regex = r" %s .+$" % sp cfg = "\n".join(findall(sp_regex, conf, M)) obj = self.parse_policies(cfg, sp) obj["connection_type"] = sp if obj: rules_lst.append(obj) sp_lst = sorted(rules_lst, key=lambda i: i["connection_type"]) return sp_lst def parse_policies(self, conf, attrib=None): """ This function triggers the parsing of policy attributes action and log. :param conf: configuration :param attrib: connection type. :return: generated rule configuration dictionary. """ a_lst = ["action", "log"] cfg_dict = self.parse_attr(conf, a_lst, match=attrib) return cfg_dict def parse_group(self, conf): """ This function triggers the parsing of 'group' attributes. :param conf: configuration. :return: generated config dictionary. """ cfg_dict = {} cfg_dict["port_group"] = self.parse_group_lst(conf, "port-group") cfg_dict["address_group"] = self.parse_group_lst(conf, "address-group") cfg_dict["network_group"] = self.parse_group_lst(conf, "network-group") return cfg_dict def parse_group_lst(self, conf, type): """ This function fetches the name of group and invoke function to parse group attributes'. :param conf: configuration data. :param type: type of group. :return: generated group list configuration. """ g_lst = [] groups = findall(r"^set firewall group " + type + " (\\S+)", conf, M) if groups: rules_lst = [] for gr in set(groups): gr_regex = r" %s .+$" % gr cfg = "\n".join(findall(gr_regex, conf, M)) obj = self.parse_groups(cfg, type, gr) obj["name"] = gr.strip("'") if obj: rules_lst.append(obj) g_lst = sorted(rules_lst, key=lambda i: i["name"]) return g_lst def parse_groups(self, conf, type, name): """ This function fetches the description and invoke the parsing of group members. :param conf: configuration. :param type: type of group. :param name: name of group. :return: generated configuration dictionary. """ a_lst = ["name", "description"] group = self.parse_attr(conf, a_lst) key = self.get_key(type) r_sub = {key[0]: self.parse_address_port_lst(conf, name, key[1])} group.update(r_sub) return group def parse_address_port_lst(self, conf, name, key): """ This function forms the regex to fetch the group members attributes. :param conf: configuration data. :param name: name of group. :param key: key value. :return: generated member list configuration. """ l_lst = [] attribs = findall(r"^.*" + name + " " + key + " (\\S+)", conf, M) if attribs: for attr in attribs: if key == "port": l_lst.append({"port": attr.strip("'")}) else: l_lst.append({"address": attr.strip("'")}) return l_lst def parse_attr(self, conf, attr_list, match=None, type=None): """ This function peforms the following: - Form the regex to fetch the required attribute config. - Type cast the output in desired format. :param conf: configuration. :param attr_list: list of attributes. :param match: parent node/attribute name. :return: generated config dictionary. """ config = {} for attrib in attr_list: regex = self.map_regex(attrib, type) if match: regex = match + " " + regex if conf: if self.is_bool(attrib): attr = self.map_regex(attrib, type) out = conf.find(attr.replace("_", "-")) dis = conf.find(attr.replace("_", "-") + " 'disable'") if out >= 1: if dis >= 1: config[attrib] = False else: config[attrib] = True else: out = search(r"^.*" + regex + " (.+)", conf, M) if out: val = out.group(1).strip("'") if self.is_num(attrib): val = int(val) config[attrib] = val return config def get_key(self, type): """ This function map the group type to member type :param type: :return: """ key = () if type == "port-group": key = ("members", "port") elif type == "address-group": key = ("members", "address") elif type == "network-group": key = ("members", "network") return key def map_regex(self, attrib, type=None): """ - This function construct the regex string. - replace the underscore with hyphen. :param attrib: attribute :return: regex string """ regex = attrib.replace("_", "-") if attrib == "all": regex = "all-ping" elif attrib == "disabled": regex = "disable" elif attrib == "broadcast": regex = "broadcast-ping" elif attrib == "send": if type == "ipv6": regex = "ipv6-send-redirects" else: regex = "send-redirects" elif attrib == "ip_src_route": if type == "ipv6": regex = "ipv6-src-route" elif attrib == "receive": if type == "ipv6": regex = "ipv6-receive-redirects" else: regex = "receive-redirects" return regex def is_num(self, attrib): """ This function looks for the attribute in predefined integer type set. :param attrib: attribute. :return: True/false. """ num_set = ("time", "code", "type", "count", "burst", "number") return True if attrib in num_set else False def get_src_route(self, attrib): """ This function looks for the attribute in predefined integer type set. :param attrib: attribute. :return: True/false. """ return "ipv6_src_route" if attrib == "ipv6" else "ip_src_route" def is_bool(self, attrib): """ This function looks for the attribute in predefined bool type set. :param attrib: attribute. :return: True/False """ bool_set = ( "all", "log", "send", "receive", "broadcast", "config_trap", "log_martians", "syn_cookies", "ip_src_route", "twa_hazards_protection", ) return True if attrib in bool_set else False 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 index 4640748..d0cfb53 100644 --- a/plugins/module_utils/network/vyos/facts/firewall_interfaces/firewall_interfaces.py +++ b/plugins/module_utils/network/vyos/facts/firewall_interfaces/firewall_interfaces.py @@ -1,196 +1,195 @@ # # -*- 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 - """ + """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 + """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/facts/firewall_rules/firewall_rules.py b/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py index 971ea6f..4424292 100644 --- a/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py +++ b/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py @@ -1,380 +1,379 @@ # # -*- 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_rules 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_rules.firewall_rules import ( Firewall_rulesArgs, ) class Firewall_rulesFacts(object): - """ The vyos firewall_rules fact class - """ + """The vyos firewall_rules fact class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = Firewall_rulesArgs.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_rules + """Populate the facts for firewall_rules :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) # split the config into instances of the resource objs = [] v6_rules = findall( r"^set firewall ipv6-name (?:\'*)(\S+)(?:\'*)", data, M ) v4_rules = findall(r"^set firewall name (?:\'*)(\S+)(?:\'*)", data, M) if v6_rules: config = self.get_rules(data, v6_rules, type="ipv6") if config: config = utils.remove_empties(config) objs.append(config) if v4_rules: config = self.get_rules(data, v4_rules, type="ipv4") if config: config = utils.remove_empties(config) objs.append(config) ansible_facts["ansible_network_resources"].pop("firewall_rules", None) facts = {} if objs: facts["firewall_rules"] = [] params = utils.validate_config( self.argument_spec, {"config": objs} ) for cfg in params["config"]: facts["firewall_rules"].append(utils.remove_empties(cfg)) ansible_facts["ansible_network_resources"].update(facts) return ansible_facts def get_rules(self, data, rules, type): """ This function performs following: - Form regex to fetch 'rule-sets' specific config from data. - Form the rule-set list based on ip address. :param data: configuration. :param rules: list of rule-sets. :param type: ip address type. :return: generated rule-sets configuration. """ r_v4 = [] r_v6 = [] for r in set(rules): rule_regex = r" %s .+$" % r.strip("'") cfg = findall(rule_regex, data, M) fr = self.render_config(cfg, r.strip("'")) fr["name"] = r.strip("'") if type == "ipv6": r_v6.append(fr) else: r_v4.append(fr) if r_v4: config = {"afi": "ipv4", "rule_sets": r_v4} if r_v6: config = {"afi": "ipv6", "rule_sets": r_v6} return config def render_config(self, conf, match): """ 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: x, conf)) a_lst = ["description", "default_action", "enable_default_log"] config = self.parse_attr(conf, a_lst, match) if not config: config = {} config["rules"] = self.parse_rules_lst(conf) return config def parse_rules_lst(self, conf): """ This function forms the regex to fetch the 'rules' with in 'rule-sets' :param conf: configuration data. :return: generated rule list configuration. """ r_lst = [] rules = findall(r"rule (?:\'*)(\d+)(?:\'*)", conf, M) if rules: rules_lst = [] for r in set(rules): r_regex = r" %s .+$" % r cfg = "\n".join(findall(r_regex, conf, M)) obj = self.parse_rules(cfg) obj["number"] = int(r) if obj: rules_lst.append(obj) r_lst = sorted(rules_lst, key=lambda i: i["number"]) return r_lst def parse_rules(self, conf): """ 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 :return: generated rule configuration dictionary. """ a_lst = [ "ipsec", "action", "protocol", "fragment", "disabled", "description", ] rule = self.parse_attr(conf, a_lst) r_sub = { "p2p": self.parse_p2p(conf), "tcp": self.parse_tcp(conf, "tcp"), "icmp": self.parse_icmp(conf, "icmp"), "time": self.parse_time(conf, "time"), "limit": self.parse_limit(conf, "limit"), "state": self.parse_state(conf, "state"), "recent": self.parse_recent(conf, "recent"), "source": self.parse_src_or_dest(conf, "source"), "destination": self.parse_src_or_dest(conf, "destination"), } rule.update(r_sub) return rule def parse_p2p(self, conf): """ This function forms the regex to fetch the 'p2p' with in 'rules' :param conf: configuration data. :return: generated rule list configuration. """ a_lst = [] applications = findall(r"p2p (?:\'*)(\d+)(?:\'*)", conf, M) if applications: app_lst = [] for r in set(applications): obj = {"application": r.strip("'")} app_lst.append(obj) a_lst = sorted(app_lst, key=lambda i: i["application"]) return a_lst def parse_src_or_dest(self, conf, attrib=None): """ This function triggers the parsing of 'source or destination' attributes. :param conf: configuration. :param attrib:'source/destination'. :return:generated source/destination configuration dictionary. """ a_lst = ["port", "address", "mac_address"] cfg_dict = self.parse_attr(conf, a_lst, match=attrib) cfg_dict["group"] = self.parse_group(conf, attrib + " group") return cfg_dict def parse_recent(self, conf, attrib=None): """ This function triggers the parsing of 'recent' attributes :param conf: configuration. :param attrib: 'recent'. :return: generated config dictionary. """ a_lst = ["time", "count"] cfg_dict = self.parse_attr(conf, a_lst, match=attrib) return cfg_dict def parse_tcp(self, conf, attrib=None): """ This function triggers the parsing of 'tcp' attributes. :param conf: configuration. :param attrib: 'tcp'. :return: generated config dictionary. """ cfg_dict = self.parse_attr(conf, ["flags"], match=attrib) return cfg_dict def parse_time(self, conf, attrib=None): """ This function triggers the parsing of 'time' attributes. :param conf: configuration. :param attrib: 'time'. :return: generated config dictionary. """ a_lst = [ "stopdate", "stoptime", "weekdays", "monthdays", "startdate", "starttime", ] cfg_dict = self.parse_attr(conf, a_lst, match=attrib) return cfg_dict def parse_state(self, conf, attrib=None): """ This function triggers the parsing of 'state' attributes. :param conf: configuration :param attrib: 'state'. :return: generated config dictionary. """ a_lst = ["new", "invalid", "related", "established"] cfg_dict = self.parse_attr(conf, a_lst, match=attrib) return cfg_dict def parse_group(self, conf, attrib=None): """ This function triggers the parsing of 'group' attributes. :param conf: configuration. :param attrib: 'group'. :return: generated config dictionary. """ a_lst = ["port_group", "address_group", "network_group"] cfg_dict = self.parse_attr(conf, a_lst, match=attrib) return cfg_dict def parse_icmp(self, conf, attrib=None): """ This function triggers the parsing of 'icmp' attributes. :param conf: configuration to be parsed. :param attrib: 'icmp'. :return: generated config dictionary. """ a_lst = ["code", "type", "type_name"] cfg_dict = self.parse_attr(conf, a_lst, match=attrib) return cfg_dict def parse_limit(self, conf, attrib=None): """ This function triggers the parsing of 'limit' attributes. :param conf: configuration to be parsed. :param attrib: 'limit' :return: generated config dictionary. """ cfg_dict = self.parse_attr(conf, ["burst"], match=attrib) cfg_dict["rate"] = self.parse_rate(conf, "rate") return cfg_dict def parse_rate(self, conf, attrib=None): """ This function triggers the parsing of 'rate' attributes. :param conf: configuration. :param attrib: 'rate' :return: generated config dictionary. """ a_lst = ["unit", "number"] cfg_dict = self.parse_attr(conf, a_lst, match=attrib) return cfg_dict def parse_attr(self, conf, attr_list, match=None): """ This function peforms the following: - Form the regex to fetch the required attribute config. - Type cast the output in desired format. :param conf: configuration. :param attr_list: list of attributes. :param match: parent node/attribute name. :return: generated config dictionary. """ config = {} for attrib in attr_list: regex = self.map_regex(attrib) if match: regex = match + " " + regex if conf: if self.is_bool(attrib): out = conf.find(attrib.replace("_", "-")) dis = conf.find(attrib.replace("_", "-") + " 'disable'") if out >= 1: if dis >= 1: config[attrib] = False else: config[attrib] = True else: out = search(r"^.*" + regex + " (.+)", conf, M) if out: val = out.group(1).strip("'") if self.is_num(attrib): val = int(val) config[attrib] = val return config def map_regex(self, attrib): """ - This function construct the regex string. - replace the underscore with hyphen. :param attrib: attribute :return: regex string """ regex = attrib.replace("_", "-") if attrib == "disabled": regex = "disable" return regex def is_bool(self, attrib): """ This function looks for the attribute in predefined bool type set. :param attrib: attribute. :return: True/False """ bool_set = ( "new", "invalid", "related", "disabled", "established", "enable_default_log", ) return True if attrib in bool_set else False def is_num(self, attrib): """ This function looks for the attribute in predefined integer type set. :param attrib: attribute. :return: True/false. """ num_set = ("time", "code", "type", "count", "burst", "number") return True if attrib in num_set else False diff --git a/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py b/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py index 4b24803..358dd9d 100644 --- a/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py +++ b/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py @@ -1,134 +1,133 @@ # # -*- 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_collections.ansible.netcommon.plugins.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 - """ + """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 + """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/l3_interfaces.py b/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py index eac8467..09ab994 100644 --- a/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py +++ b/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py @@ -1,143 +1,142 @@ # # -*- 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_collections.ansible.netcommon.plugins.module_utils.network.common import ( utils, ) from ansible.module_utils.six import iteritems from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( get_ip_address_version, ) 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 - """ + """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 + """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 = get_ip_address_version(item.split("/")[0]) 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/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/vyos/facts/lag_interfaces/lag_interfaces.py index 9056294..ff02cb0 100644 --- a/plugins/module_utils/network/vyos/facts/lag_interfaces/lag_interfaces.py +++ b/plugins/module_utils/network/vyos/facts/lag_interfaces/lag_interfaces.py @@ -1,144 +1,143 @@ # # -*- 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 lag_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.lag_interfaces.lag_interfaces import ( Lag_interfacesArgs, ) class Lag_interfacesFacts(object): - """ The vyos lag_interfaces fact class - """ + """The vyos lag_interfaces fact class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = Lag_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 lag_interfaces + """Populate the facts for lag_interfaces :param module: the module instance :param connection: the device connection :param data: previously collected conf :rtype: dictionary :returns: facts """ if not data: data = connection.get_config() objs = [] lag_names = findall(r"^set interfaces bonding (\S+)", data, M) if lag_names: for lag in set(lag_names): lag_regex = r" %s .+$" % lag cfg = findall(lag_regex, data, M) obj = self.render_config(cfg) members = [] member = {} group_regex = r".*eth.* '%s'" % lag g_cfg = findall(group_regex, data, M) for item in g_cfg: output = search("^set interfaces ethernet (\\S+)", item, M) if output: member["member"] = output.group(1).strip("'") members.append(member) obj["name"] = lag.strip("'") if members: obj["members"] = members if obj: objs.append(obj) facts = {} if objs: facts["lag_interfaces"] = [] params = utils.validate_config( self.argument_spec, {"config": objs} ) for cfg in params["config"]: facts["lag_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 """ arp_monitor_conf = "\n".join( filter(lambda x: ("arp-monitor" in x), conf) ) hash_policy_conf = "\n".join( filter(lambda x: ("hash-policy" in x), conf) ) lag_conf = "\n".join(filter(lambda x: ("bond" in x), conf)) config = self.parse_attribs(["mode", "primary"], lag_conf) config["arp_monitor"] = self.parse_arp_monitor(arp_monitor_conf) config["hash_policy"] = self.parse_hash_policy(hash_policy_conf) return utils.remove_empties(config) def parse_attribs(self, attribs, conf): config = {} for item in attribs: value = utils.parse_conf_arg(conf, item) if value: config[item] = value.strip("'") else: config[item] = None return utils.remove_empties(config) def parse_arp_monitor(self, conf): arp_monitor = None if conf: arp_monitor = {} target_list = [] interval = search(r"^.*arp-monitor interval (.+)", conf, M) targets = findall(r"^.*arp-monitor target '(.+)'", conf, M) if targets: for target in targets: target_list.append(target) arp_monitor["target"] = target_list if interval: value = interval.group(1).strip("'") arp_monitor["interval"] = int(value) return arp_monitor def parse_hash_policy(self, conf): hash_policy = None if conf: hash_policy = search(r"^.*hash-policy (.+)", conf, M) hash_policy = hash_policy.group(1).strip("'") return hash_policy diff --git a/plugins/module_utils/network/vyos/facts/lldp_global/lldp_global.py b/plugins/module_utils/network/vyos/facts/lldp_global/lldp_global.py index 3c7e2f9..22b6bc8 100644 --- a/plugins/module_utils/network/vyos/facts/lldp_global/lldp_global.py +++ b/plugins/module_utils/network/vyos/facts/lldp_global/lldp_global.py @@ -1,116 +1,115 @@ # # -*- 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 lldp_global 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_collections.ansible.netcommon.plugins.module_utils.network.common import ( utils, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.lldp_global.lldp_global import ( Lldp_globalArgs, ) class Lldp_globalFacts(object): - """ The vyos lldp_global fact class - """ + """The vyos lldp_global fact class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = Lldp_globalArgs.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 lldp_global + """Populate the facts for lldp_global :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() objs = {} lldp_output = findall(r"^set service lldp (\S+)", data, M) if lldp_output: for item in set(lldp_output): lldp_regex = r" %s .+$" % item cfg = findall(lldp_regex, data, M) obj = self.render_config(cfg) if obj: objs.update(obj) lldp_service = findall(r"^set service (lldp)?('lldp')", data, M) if lldp_service or lldp_output: lldp_obj = {} lldp_obj["enable"] = True objs.update(lldp_obj) facts = {} params = utils.validate_config(self.argument_spec, {"config": objs}) facts["lldp_global"] = utils.remove_empties(params["config"]) 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 - """ + 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 + """ protocol_conf = "\n".join( filter(lambda x: ("legacy-protocols" in x), conf) ) att_conf = "\n".join( filter(lambda x: ("legacy-protocols" not in x), conf) ) config = self.parse_attribs(["snmp", "address"], att_conf) config["legacy_protocols"] = self.parse_protocols(protocol_conf) return utils.remove_empties(config) def parse_protocols(self, conf): protocol_support = None if conf: protocols = findall(r"^.*legacy-protocols (.+)", conf, M) if protocols: protocol_support = [] for protocol in protocols: protocol_support.append(protocol.strip("'")) return protocol_support def parse_attribs(self, attribs, conf): config = {} for item in attribs: value = utils.parse_conf_arg(conf, item) if value: config[item] = value.strip("'") else: config[item] = None return utils.remove_empties(config) diff --git a/plugins/module_utils/network/vyos/facts/lldp_interfaces/lldp_interfaces.py b/plugins/module_utils/network/vyos/facts/lldp_interfaces/lldp_interfaces.py index dcfbc6e..64d6a79 100644 --- a/plugins/module_utils/network/vyos/facts/lldp_interfaces/lldp_interfaces.py +++ b/plugins/module_utils/network/vyos/facts/lldp_interfaces/lldp_interfaces.py @@ -1,155 +1,154 @@ # # -*- 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 lldp_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.lldp_interfaces.lldp_interfaces import ( Lldp_interfacesArgs, ) class Lldp_interfacesFacts(object): - """ The vyos lldp_interfaces fact class - """ + """The vyos lldp_interfaces fact class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = Lldp_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 lldp_interfaces + """Populate the facts for lldp_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() objs = [] lldp_names = findall(r"^set service lldp interface (\S+)", data, M) if lldp_names: for lldp in set(lldp_names): lldp_regex = r" %s .+$" % lldp cfg = findall(lldp_regex, data, M) obj = self.render_config(cfg) obj["name"] = lldp.strip("'") if obj: objs.append(obj) facts = {} if objs: facts["lldp_interfaces"] = objs ansible_facts["ansible_network_resources"].update(facts) 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 """ config = {} location = {} civic_conf = "\n".join(filter(lambda x: ("civic-based" in x), conf)) elin_conf = "\n".join(filter(lambda x: ("elin" in x), conf)) coordinate_conf = "\n".join( filter(lambda x: ("coordinate-based" in x), conf) ) disable = "\n".join(filter(lambda x: ("disable" in x), conf)) coordinate_based_conf = self.parse_attribs( ["altitude", "datum", "longitude", "latitude"], coordinate_conf ) elin_based_conf = self.parse_lldp_elin_based(elin_conf) civic_based_conf = self.parse_lldp_civic_based(civic_conf) if disable: config["enable"] = False if coordinate_conf: location["coordinate_based"] = coordinate_based_conf config["location"] = location elif civic_based_conf: location["civic_based"] = civic_based_conf config["location"] = location elif elin_conf: location["elin"] = elin_based_conf config["location"] = location return utils.remove_empties(config) def parse_attribs(self, attribs, conf): config = {} for item in attribs: value = utils.parse_conf_arg(conf, item) if value: value = value.strip("'") if item == "altitude": value = int(value) config[item] = value else: config[item] = None return utils.remove_empties(config) def parse_lldp_civic_based(self, conf): civic_based = None if conf: civic_info_list = [] civic_add_list = findall(r"^.*civic-based ca-type (.+)", conf, M) if civic_add_list: for civic_add in civic_add_list: ca = civic_add.split(" ") c_add = {} c_add["ca_type"] = int(ca[0].strip("'")) c_add["ca_value"] = ca[2].strip("'") civic_info_list.append(c_add) country_code = search( r"^.*civic-based country-code (.+)", conf, M ) civic_based = {} civic_based["ca_info"] = civic_info_list civic_based["country_code"] = country_code.group(1).strip("'") return civic_based def parse_lldp_elin_based(self, conf): elin_based = None if conf: e_num = search(r"^.* elin (.+)", conf, M) elin_based = e_num.group(1).strip("'") return elin_based diff --git a/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py index d62fa9a..b0c9df2 100644 --- a/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py +++ b/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py @@ -1,499 +1,501 @@ # # -*- 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 ospfv2 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.ospfv2.ospfv2 import ( Ospfv2Args, ) class Ospfv2Facts(object): - """ The vyos ospfv2 fact class - """ + """The vyos ospfv2 fact class""" def __init__( - self, module, subspec="config", options="options", + self, + module, + subspec="config", + options="options", ): self._module = module self.argument_spec = Ospfv2Args.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 ospfv2 + """Populate the facts for ospfv2 :param connection: the device connection :param ansible_facts: Facts dictionary :param data: previously collected conf :rtype: dictionary :returns: facts """ if not data: data = self.get_device_data(connection) # typically data is populated from the current device configuration # data = connection.get('show running-config | section ^interface') # using mock data instead objs = {} ospfv2 = findall(r"^set protocols ospf (.+)", data, M) if ospfv2: objs = self.render_config(ospfv2) facts = {} params = utils.validate_config(self.argument_spec, {"config": objs}) facts["ospfv2"] = utils.remove_empties(params["config"]) ansible_facts["ansible_network_resources"].update(facts) return ansible_facts def render_config(self, conf): """ Render config as dictionary structure :param conf: The configuration :returns: The generated config """ conf = "\n".join(filter(lambda x: x, conf)) a_lst = ["default_metric", "log_adjacency_changes"] config = self.parse_attr(conf, a_lst) if not config: config = {} config["timers"] = self.parse_timers(conf) config["auto_cost"] = self.parse_auto_cost(conf) config["distance"] = self.parse_distance(conf) config["max_metric"] = self.parse_max_metric(conf) config["default_information"] = self.parse_def_info(conf) config["route_map"] = self.parse_leaf_list(conf, "route-map") config["mpls_te"] = self.parse_attrib(conf, "mpls_te", "mpls-te") config["areas"] = self.parse_attrib_list(conf, "area", "area_id") config["parameters"] = self.parse_attrib( conf, "parameters", "parameters" ) config["neighbor"] = self.parse_attrib_list( conf, "neighbor", "neighbor_id" ) config["passive_interface"] = self.parse_leaf_list( conf, "passive-interface" ) config["redistribute"] = self.parse_attrib_list( conf, "redistribute", "route_type" ) config["passive_interface_exclude"] = self.parse_leaf_list( conf, "passive-interface-exclude" ) return config def parse_timers(self, conf): """ This function triggers the parsing of 'timers' attributes :param conf: configuration :return: generated config dictionary """ cfg_dict = {} cfg_dict["refresh"] = self.parse_refresh(conf, "refresh") cfg_dict["throttle"] = self.parse_throttle(conf, "spf") return cfg_dict def parse_throttle(self, conf, attrib=None): """ This function triggers the parsing of 'throttle' attributes :param conf: configuration :param attrib: 'spf' :return: generated config dictionary """ cfg_dict = {} cfg_dict[attrib] = self.parse_attrib(conf, attrib, match=attrib) return cfg_dict def parse_refresh(self, conf, attrib=None): """ This function triggers the parsing of 'refresh' attributes :param conf: configuration :param attrib: 'refresh' :return: generated config dictionary """ cfg_dict = self.parse_attr(conf, ["timers"], match=attrib) return cfg_dict def parse_leaf_list(self, conf, attrib): """ This function forms the regex to fetch the listed attributes from the configuration data :param conf: configuration data :param attrib: attribute name :return: generated rule list configuration """ lst = [] items = findall(r"^" + attrib + " (?:'*)(\\S+)(?:'*)", conf, M) if items: for i in set(items): lst.append(i.strip("'")) lst.sort() return lst def parse_distance(self, conf, attrib=None): """ This function triggers the parsing of 'distance' attributes :param conf: configuration :param attrib: attribute name :return: generated config dictionary """ cfg_dict = self.parse_attr(conf, ["global"], match=attrib) cfg_dict["ospf"] = self.parse_ospf(conf, "ospf") return cfg_dict def parse_ospf(self, conf, attrib=None): """ This function triggers the parsing of 'distance ospf' attributes :param conf: configuration :param attrib: 'ospf' :return: generated config dictionary """ cfg_dict = self.parse_attrib(conf, "ospf", match=attrib) return cfg_dict def parse_max_metric(self, conf): """ This function triggers the parsing of 'max_metric' attributes :param conf: configuration :return: generated config dictionary """ cfg_dict = {} cfg_dict["router_lsa"] = self.parse_attrib( conf, "router_lsa", match="router-lsa" ) return cfg_dict def parse_auto_cost(self, conf, attrib=None): """ This function triggers the parsing of 'auto_cost' attributes :param conf: configuration :param attrib: attribute name :return: generated config dictionary """ cfg_dict = self.parse_attr(conf, ["reference_bandwidth"], match=attrib) return cfg_dict def parse_def_info(self, conf): """ This function triggers the parsing of 'default_information' attributes :param conf: configuration :return: generated config dictionary """ cfg_dict = {} cfg_dict["originate"] = self.parse_attrib( conf, "originate", "originate" ) return cfg_dict def parse_area(self, conf, area_id): """ This function triggers the parsing of 'area' attributes. :param conf: configuration data :param area_id: area identity :return: generated rule configuration dictionary. """ rule = self.parse_attrib(conf, "area_id", match=area_id) r_sub = { "area_type": self.parse_area_type(conf, "area-type"), "network": self.parse_network(conf), "range": self.parse_attrib_list(conf, "range", "address"), "virtual_link": self.parse_attrib_list( conf, "virtual-link", "address" ), } rule.update(r_sub) return rule def parse_key(self, conf, key_id): """ This function triggers the parsing of 'area' attributes. :param conf: configuration data :param area_id: area identity :return: generated rule configuration dictionary. """ rule = self.parse_attrib(conf, "key_id", match=key_id) return rule def parse_area_type(self, conf, attrib=None): """ This function triggers the parsing of 'area_type' attributes :param conf: configuration :param attrib: 'area-type' :return: generated config dictionary """ cfg_dict = self.parse_attr(conf, ["normal"], match=attrib) cfg_dict["nssa"] = self.parse_attrib(conf, "nssa", match="nssa") cfg_dict["stub"] = self.parse_attrib(conf, "stub", match="stub") return cfg_dict def parse_network(self, conf): """ This function forms the regex to fetch the 'network' :param conf: configuration data :return: generated rule list configuration """ a_lst = [] applications = findall(r"network (.+)", conf, M) if applications: app_lst = [] for r in set(applications): obj = {"address": r.strip("'")} app_lst.append(obj) a_lst = sorted(app_lst, key=lambda i: i["address"]) return a_lst def parse_vlink(self, conf): """ This function triggers the parsing of 'virtual_link' attributes :param conf: configuration data :return: generated rule configuration dictionary """ rule = self.parse_attrib(conf, "vlink") r_sub = { "authentication": self.parse_authentication(conf, "authentication") } rule.update(r_sub) return rule def parse_authentication(self, conf, attrib=None): """ This function triggers the parsing of 'authentication' attributes. :param conf: configuration :param attrib: 'authentication' :return: generated config dictionary """ cfg_dict = self.parse_attr(conf, ["plaintext_password"], match=attrib) cfg_dict["md5"] = self.parse_attrib_list(conf, "key-id", "key_id") return cfg_dict def parse_attrib_list(self, conf, attrib, param): """ This function forms the regex to fetch the listed attributes from config :param conf: configuration data :param attrib: attribute name :param param: parameter data :return: generated rule list configuration """ r_lst = [] if attrib == "area": items = findall( r"^" + attrib.replace("_", "-") + " (?:'*)(\\S+)(?:'*)", conf, M, ) elif attrib == "key-id": items = findall( r"^.*" + attrib.replace("_", "-") + " (?:'*)(\\S+)(?:'*)", conf, M, ) else: items = findall(r"" + attrib + " (?:'*)(\\S+)(?:'*)", conf, M) if items: a_lst = [] for item in set(items): i_regex = r" %s .+$" % item cfg = "\n".join(findall(i_regex, conf, M)) if attrib == "area": obj = self.parse_area(cfg, item) elif attrib == "virtual-link": obj = self.parse_vlink(cfg) elif attrib == "key-id": obj = self.parse_key(cfg, item) else: obj = self.parse_attrib(cfg, attrib) obj[param] = item.strip("'") if obj: a_lst.append(obj) r_lst = sorted(a_lst, key=lambda i: i[param]) return r_lst def parse_attrib(self, conf, param, match=None): """ This function triggers the parsing of 'ospf' attributes :param conf: configuration data :return: generated configuration dictionary """ param_lst = { "key_id": ["md5_key"], "mpls_te": ["enabled", "router_address"], "area_id": ["shortcut", "authentication"], "neighbor": ["priority", "poll_interval"], "stub": ["set", "default_cost", "no_summary"], "range": ["cost", "substitute", "not_advertise"], "ospf": ["external", "inter_area", "intra_area"], "spf": ["delay", "max_holdtime", "initial_holdtime"], "redistribute": ["metric", "metric_type", "route_map"], "nssa": ["set", "translate", "default_cost", "no_summary"], "config_routes": ["default_metric", "log_adjacency_changes"], "originate": ["always", "metric", "metric_type", "route_map"], "router_lsa": ["administrative", "on_shutdown", "on_startup"], "parameters": [ "abr_type", "opaque_lsa", "router_id", "rfc1583_compatibility", ], "vlink": [ "dead_interval", "hello_interval", "transmit_delay", "retransmit_interval", ], } cfg_dict = self.parse_attr(conf, param_lst[param], match) return cfg_dict def parse_attr(self, conf, attr_list, match=None): """ This function peforms the following: - Form the regex to fetch the required attribute config. - Type cast the output in desired format. :param conf: configuration. :param attr_list: list of attributes. :param match: parent node/attribute name. :return: generated config dictionary. """ config = {} for attrib in attr_list: regex = self.map_regex(attrib) if match: regex = match.replace("_", "-") + " " + regex if conf: if self.is_bool(attrib): out = conf.find(attrib.replace("_", "-")) dis = conf.find(attrib.replace("_", "-") + " 'disable'") if match: if attrib == "set" and conf.find(match) >= 1: config[attrib] = True en = conf.find(match + " 'enable'") if out >= 1: if dis >= 1: config[attrib] = False else: config[attrib] = True elif match and en >= 1: config[attrib] = True else: out = search(r"^.*" + regex + " (.+)", conf, M) if out: val = out.group(1).strip("'") if self.is_num(attrib): val = int(val) config[attrib] = val return config def map_regex(self, attrib): """ - This function construct the regex string. - replace the underscore with hyphen. :param attrib: attribute :return: regex string """ return ( "disable" if attrib == "disabled" else ( "enable" if attrib == "enabled" else ( "area" if attrib == "area_id" else attrib.replace("_", "-") ) ) ) def is_bool(self, attrib): """ This function looks for the attribute in predefined bool type set. :param attrib: attribute. :return: True/False """ bool_set = ( "set", "always", "normal", "enabled", "opaque_lsa", "not_advertise", "administrative", "rfc1583_compatibility", ) return True if attrib in bool_set else False def is_num(self, attrib): """ This function looks for the attribute in predefined integer type set. :param attrib: attribute. :return: True/false. """ num_set = ( "ospf", "delay", "metric", "inter_area", "intra_area", "on_startup", "metric_type", "on_shutdown", "max_holdtime", "poll_interval", "default_metric", "initial_holdtime", "key_id", ) return True if attrib in num_set else False diff --git a/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py index 457a963..414c65e 100644 --- a/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py +++ b/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py @@ -1,213 +1,212 @@ # # -*- 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 ospfv3 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.ospfv3.ospfv3 import ( Ospfv3Args, ) class Ospfv3Facts(object): - """ The vyos ospfv3 fact class - """ + """The vyos ospfv3 fact class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = Ospfv3Args.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 ospfv3 + """Populate the facts for ospfv3 :param connection: the device connection :param ansible_facts: Facts dictionary :param data: previously collected conf :rtype: dictionary :returns: facts """ if not data: data = self.get_device_data(connection) # typically data is populated from the current device configuration # data = connection.get('show running-config | section ^interface') # using mock data instead objs = {} ospfv3 = findall(r"^set protocols ospfv3 (.+)", data, M) if ospfv3: objs = self.render_config(ospfv3) facts = {} params = utils.validate_config(self.argument_spec, {"config": objs}) facts["ospfv3"] = utils.remove_empties(params["config"]) ansible_facts["ansible_network_resources"].update(facts) return ansible_facts def render_config(self, conf): """ Render config as dictionary structure :param conf: The configuration :returns: The generated config """ conf = "\n".join(filter(lambda x: x, conf)) config = {} config["parameters"] = self.parse_attrib( conf, "parameters", "parameters" ) config["areas"] = self.parse_attrib_list(conf, "area", "area_id") config["redistribute"] = self.parse_attrib_list( conf, "redistribute", "route_type" ) return config def parse_attrib_list(self, conf, attrib, param): """ This function forms the regex to fetch the listed attributes from config :param conf: configuration data :param attrib: attribute name :param param: parameter data :return: generated rule list configuration """ r_lst = [] if attrib == "area": items = findall(r"^" + attrib + " (?:'*)(\\S+)(?:'*)", conf, M) else: items = findall(r"" + attrib + " (?:'*)(\\S+)(?:'*)", conf, M) if items: a_lst = [] for item in set(items): i_regex = r" %s .+$" % item cfg = "\n".join(findall(i_regex, conf, M)) if attrib == "area": obj = self.parse_area(cfg, item) else: obj = self.parse_attrib(cfg, attrib) obj[param] = item.strip("'") if obj: a_lst.append(obj) r_lst = sorted(a_lst, key=lambda i: i[param]) return r_lst def parse_area(self, conf, area_id): """ This function triggers the parsing of 'area' attributes. :param conf: configuration data :param area_id: area identity :return: generated rule configuration dictionary. """ rule = self.parse_attrib(conf, "area_id", match=area_id) r_sub = {"range": self.parse_attrib_list(conf, "range", "address")} rule.update(r_sub) return rule def parse_attrib(self, conf, param, match=None): """ This function triggers the parsing of 'ospf' attributes :param conf: configuration data :return: generated configuration dictionary """ param_lst = { "area_id": ["export_list", "import_list"], "redistribute": ["route_map"], "range": ["advertise", "not_advertise"], "parameters": ["router_id"], } cfg_dict = self.parse_attr(conf, param_lst[param], match) return cfg_dict def parse_attr(self, conf, attr_list, match=None): """ This function peforms the following: - Form the regex to fetch the required attribute config. - Type cast the output in desired format. :param conf: configuration. :param attr_list: list of attributes. :param match: parent node/attribute name. :return: generated config dictionary. """ config = {} for attrib in attr_list: regex = self.map_regex(attrib) if match: regex = match.replace("_", "-") + " " + regex if conf: if self.is_bool(attrib): out = conf.find(attrib.replace("_", "-")) dis = conf.find(attrib.replace("_", "-") + " 'disable'") if match: en = conf.find(match + " 'enable'") if out >= 1: if dis >= 1: config[attrib] = False else: config[attrib] = True elif match and en >= 1: config[attrib] = True else: out = search(r"^.*" + regex + " (.+)", conf, M) if out: val = out.group(1).strip("'") if self.is_num(attrib): val = int(val) config[attrib] = val return config def map_regex(self, attrib): """ - This function construct the regex string. - replace the underscore with hyphen. :param attrib: attribute :return: regex string """ return ( "disable" if attrib == "disabled" else "enable" if attrib == "enabled" else attrib.replace("_", "-") ) def is_bool(self, attrib): """ This function looks for the attribute in predefined bool type set. :param attrib: attribute. :return: True/False """ bool_set = ("enabled", "advertise", "not_advertise") return True if attrib in bool_set else False def is_num(self, attrib): """ This function looks for the attribute in predefined integer type set. :param attrib: attribute. :return: True/false. """ num_set = "ospf" return True if attrib in num_set else False diff --git a/plugins/module_utils/network/vyos/facts/static_routes/static_routes.py b/plugins/module_utils/network/vyos/facts/static_routes/static_routes.py index 0004947..bce2731 100644 --- a/plugins/module_utils/network/vyos/facts/static_routes/static_routes.py +++ b/plugins/module_utils/network/vyos/facts/static_routes/static_routes.py @@ -1,181 +1,180 @@ # # -*- 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 static_routes 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.static_routes.static_routes import ( Static_routesArgs, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( get_route_type, ) class Static_routesFacts(object): - """ The vyos static_routes fact class - """ + """The vyos static_routes fact class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = Static_routesArgs.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 static_routes + """Populate the facts for static_routes :param connection: the device connection :param ansible_facts: Facts dictionary :param data: previously collected conf :rtype: dictionary :returns: facts """ if not data: data = self.get_device_data(connection) # typically data is populated from the current device configuration # data = connection.get('show running-config | section ^interface') # using mock data instead objs = [] r_v4 = [] r_v6 = [] af = [] static_routes = findall( r"set protocols static route(6)? (\S+)", data, M ) if static_routes: for route in set(static_routes): route_regex = r" %s .+$" % route[1] cfg = findall(route_regex, data, M) sr = self.render_config(cfg) sr["dest"] = route[1].strip("'") afi = self.get_afi(sr["dest"]) if afi == "ipv4": r_v4.append(sr) else: r_v6.append(sr) if r_v4: afi_v4 = {"afi": "ipv4", "routes": r_v4} af.append(afi_v4) if r_v6: afi_v6 = {"afi": "ipv6", "routes": r_v6} af.append(afi_v6) config = {"address_families": af} if config: objs.append(config) ansible_facts["ansible_network_resources"].pop("static_routes", None) facts = {} if objs: facts["static_routes"] = [] params = utils.validate_config( self.argument_spec, {"config": objs} ) for cfg in params["config"]: facts["static_routes"].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 """ next_hops_conf = "\n".join(filter(lambda x: ("next-hop" in x), conf)) blackhole_conf = "\n".join(filter(lambda x: ("blackhole" in x), conf)) routes_dict = { "blackhole_config": self.parse_blackhole(blackhole_conf), "next_hops": self.parse_next_hop(next_hops_conf), } return routes_dict def parse_blackhole(self, conf): blackhole = None if conf: distance = search(r"^.*blackhole distance (.\S+)", conf, M) bh = conf.find("blackhole") if distance is not None: blackhole = {} value = distance.group(1).strip("'") blackhole["distance"] = int(value) elif bh: blackhole = {} blackhole["type"] = "blackhole" return blackhole def get_afi(self, address): route_type = get_route_type(address) if route_type == "route": return "ipv4" elif route_type == "route6": return "ipv6" def parse_next_hop(self, conf): nh_list = None if conf: nh_list = [] hop_list = findall(r"^.*next-hop (.+)", conf, M) if hop_list: for hop in hop_list: distance = search(r"^.*distance (.\S+)", hop, M) interface = search(r"^.*interface (.\S+)", hop, M) dis = hop.find("disable") hop_info = hop.split(" ") nh_info = { "forward_router_address": hop_info[0].strip("'") } if interface: nh_info["interface"] = interface.group(1).strip("'") if distance: value = distance.group(1).strip("'") nh_info["admin_distance"] = int(value) elif dis >= 1: nh_info["enabled"] = False for element in nh_list: if ( element["forward_router_address"] == nh_info["forward_router_address"] ): if "interface" in nh_info.keys(): element["interface"] = nh_info["interface"] if "admin_distance" in nh_info.keys(): element["admin_distance"] = nh_info[ "admin_distance" ] if "enabled" in nh_info.keys(): element["enabled"] = nh_info["enabled"] nh_info = None if nh_info is not None: nh_list.append(nh_info) return nh_list diff --git a/plugins/module_utils/network/vyos/utils/utils.py b/plugins/module_utils/network/vyos/utils/utils.py index 7e0f3cc..bcf6fc2 100644 --- a/plugins/module_utils/network/vyos/utils/utils.py +++ b/plugins/module_utils/network/vyos/utils/utils.py @@ -1,269 +1,268 @@ # -*- 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 from ansible.module_utils.six import iteritems from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ( ipaddress, ) def search_obj_in_list(name, lst, key="name"): if lst: for item in lst: if item[key] == name: return item return None def get_interface_type(interface): - """Gets the type of 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 def get_lst_diff_for_dicts(want, have, lst): """ This function generates a list containing values that are only in want and not in list in have dict :param want: dict object to want :param have: dict object to have :param lst: list the diff on :return: new list object with values which are only in want. """ if not have: diff = want.get(lst) or [] else: want_elements = want.get(lst) or {} have_elements = have.get(lst) or {} diff = list_diff_want_only(want_elements, have_elements) return diff def get_lst_same_for_dicts(want, have, lst): """ This function generates a list containing values that are common for list in want and list in have dict :param want: dict object to want :param have: dict object to have :param lst: list the comparison on :return: new list object with values which are common in want and have. """ diff = None if want and have: want_list = want.get(lst) or {} have_list = have.get(lst) or {} diff = [ i for i in want_list and have_list if i in have_list and i in want_list ] return diff def list_diff_have_only(want_list, have_list): """ This function generated the list containing values that are only in have list. :param want_list: :param have_list: :return: new list with values which are only in have list """ if have_list and not want_list: diff = have_list elif not have_list: diff = None else: diff = [ i for i in have_list + want_list if i in have_list and i not in want_list ] return diff def list_diff_want_only(want_list, have_list): """ This function generated the list containing values that are only in want list. :param want_list: :param have_list: :return: new list with values which are only in want list """ if have_list and not want_list: diff = None elif not have_list: diff = want_list else: diff = [ i for i in have_list + want_list if i in want_list and i not in have_list ] return diff def search_dict_tv_in_list(d_val1, d_val2, lst, key1, key2): """ This function return the dict object if it exist in list. :param d_val1: :param d_val2: :param lst: :param key1: :param key2: :return: """ obj = next( ( item for item in lst if item[key1] == d_val1 and item[key2] == d_val2 ), None, ) if obj: return obj else: return None def key_value_in_dict(have_key, have_value, want_dict): """ This function checks whether the key and values exist in dict :param have_key: :param have_value: :param want_dict: :return: """ for key, value in iteritems(want_dict): if key == have_key and value == have_value: return True return False def is_dict_element_present(dict, key): """ This function checks whether the key is present in dict. :param dict: :param key: :return: """ for item in dict: if item == key: return True return False def get_ip_address_version(address): """ This function returns the version of IP address :param address: IP address :return: """ try: address = unicode(address) except NameError: address = str(address) version = ipaddress.ip_address(address.split("/")[0]).version return version def get_route_type(address): """ This function returns the route type based on IP address :param address: :return: """ version = get_ip_address_version(address) if version == 6: return "route6" elif version == 4: return "route" def _bool_to_str(val): """ This function converts the bool value into string. :param val: bool value. :return: enable/disable. """ return ( "enable" if str(val) == "True" else "disable" if str(val) == "False" else val ) def _is_w_same(w, h, key): """ This function checks whether the key value is same in desired and target config dictionary. :param w: base config. :param h: target config. :param key:attribute name. :return: True/False. """ return True if h and key in h and h[key] == w[key] else False def _in_target(h, key): """ This function checks whether the target exist and key present in target config. :param h: target config. :param key: attribute name. :return: True/False. """ return True if h and key in h else False diff --git a/plugins/modules/vyos_banner.py b/plugins/modules/vyos_banner.py index 8d1e5ff..9fcaf87 100644 --- a/plugins/modules/vyos_banner.py +++ b/plugins/modules/vyos_banner.py @@ -1,199 +1,198 @@ #!/usr/bin/python # -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function __metaclass__ = type # (c) 2017, Ansible by Red Hat, inc # # This file is part of Ansible by Red Hat # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # DOCUMENTATION = """ module: vyos_banner author: Trishna Guha (@trishnaguha) short_description: Manage multiline banners on VyOS devices description: - This will configure both pre-login and post-login banners on remote devices running VyOS. It allows playbooks to add or remote banner text from the active running configuration. version_added: 1.0.0 notes: - Tested against VyOS 1.1.8 (helium). - This module works with connection C(network_cli). See L(the VyOS OS Platform Options,../network/user_guide/platform_vyos.html). options: banner: description: - Specifies which banner that should be configured on the remote device. required: true choices: - pre-login - post-login type: str text: description: - The banner text that should be present in the remote device running configuration. This argument accepts a multiline string, with no empty lines. Requires I(state=present). type: str state: description: - Specifies whether or not the configuration is present in the current devices active running configuration. default: present type: str choices: - present - absent extends_documentation_fragment: - vyos.vyos.vyos """ EXAMPLES = """ - name: configure the pre-login banner vyos.vyos.vyos_banner: banner: pre-login text: | this is my pre-login banner that contains a multiline string state: present - name: remove the post-login banner vyos.vyos.vyos_banner: banner: post-login state: absent """ RETURN = """ commands: description: The list of configuration mode commands to send to the device returned: always type: list sample: - banner pre-login - this is my pre-login banner - that contains a multiline - string """ import re from ansible.module_utils.basic import AnsibleModule from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( get_config, load_config, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( vyos_argument_spec, ) def spec_to_commands(updates, module): commands = list() want, have = updates state = module.params["state"] if state == "absent": if have.get("state") != "absent" or ( have.get("state") != "absent" and "text" in have.keys() and have["text"] ): commands.append( "delete system login banner %s" % module.params["banner"] ) elif state == "present": if want["text"] and want["text"].encode().decode( "unicode_escape" ) != have.get("text"): banner_cmd = ( "set system login banner %s " % module.params["banner"] ) banner_cmd += want["text"].strip() commands.append(banner_cmd) return commands def config_to_dict(module): data = get_config(module) output = None obj = {"banner": module.params["banner"], "state": "absent"} for line in data.split("\n"): if line.startswith("set system login banner %s" % obj["banner"]): match = re.findall(r"%s (.*)" % obj["banner"], line, re.M) output = match if output: obj["text"] = output[0].encode().decode("unicode_escape") obj["state"] = "present" return obj def map_params_to_obj(module): text = module.params["text"] if text: text = "%r" % (str(text).strip()) return { "banner": module.params["banner"], "text": text, "state": module.params["state"], } def main(): - """ main entry point for module execution - """ + """main entry point for module execution""" argument_spec = dict( banner=dict(required=True, choices=["pre-login", "post-login"]), text=dict(), state=dict(default="present", choices=["present", "absent"]), ) argument_spec.update(vyos_argument_spec) required_if = [("state", "present", ("text",))] module = AnsibleModule( argument_spec=argument_spec, required_if=required_if, supports_check_mode=True, ) warnings = list() result = {"changed": False} if warnings: result["warnings"] = warnings want = map_params_to_obj(module) have = config_to_dict(module) commands = spec_to_commands((want, have), module) result["commands"] = commands if commands: commit = not module.check_mode load_config(module, commands, commit=commit) result["changed"] = True module.exit_json(**result) if __name__ == "__main__": main() diff --git a/plugins/modules/vyos_interface.py b/plugins/modules/vyos_interface.py index 21b0dc2..04fd85d 100644 --- a/plugins/modules/vyos_interface.py +++ b/plugins/modules/vyos_interface.py @@ -1,555 +1,554 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2017, Ansible by Red Hat, inc # # This file is part of Ansible by Red Hat # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = """ module: vyos_interface author: Ganesh Nalawade (@ganeshrn) short_description: (deprecated, removed after 2022-06-01) Manage Interface on VyOS network devices description: - This module provides declarative management of Interfaces on VyOS network devices. version_added: 1.0.0 deprecated: alternative: vyos_interfaces why: Updated modules released with more functionality. removed_at_date: '2022-06-01' notes: - Tested against VYOS 1.1.7 options: name: description: - Name of the Interface. type: str description: description: - Description of Interface. type: str enabled: description: - Interface link status. type: bool default: True speed: description: - Interface link speed. type: str mtu: description: - Maximum size of transmit packet. type: int duplex: description: - Interface link status. type: str choices: - full - half - auto delay: description: - Time in seconds to wait before checking for the operational state on remote device. This wait is applicable for operational state argument which are I(state) with values C(up)/C(down) and I(neighbors). default: 10 type: int neighbors: description: - Check the operational state of given interface C(name) for LLDP neighbor. - The following suboptions are available. type: list elements: dict suboptions: host: description: - LLDP neighbor host for given interface C(name). type: str port: description: - LLDP neighbor port to which given interface C(name) is connected. type: str aggregate: description: List of Interfaces definitions. type: list elements: dict suboptions: name: description: - Name of the Interface. required: true type: str description: description: - Description of Interface. type: str enabled: description: - Interface link status. type: bool speed: description: - Interface link speed. type: str mtu: description: - Maximum size of transmit packet. type: int duplex: description: - Interface link status. type: str choices: - full - half - auto delay: description: - Time in seconds to wait before checking for the operational state on remote device. This wait is applicable for operational state argument which are I(state) with values C(up)/C(down) and I(neighbors). type: int neighbors: description: - Check the operational state of given interface C(name) for LLDP neighbor. - The following suboptions are available. type: list elements: dict suboptions: host: description: - LLDP neighbor host for given interface C(name). type: str port: description: - LLDP neighbor port to which given interface C(name) is connected. type: str state: description: - State of the Interface configuration, C(up) means present and operationally up and C(down) means present and operationally C(down) type: str choices: - present - absent - up - down state: description: - State of the Interface configuration, C(up) means present and operationally up and C(down) means present and operationally C(down) default: present type: str choices: - present - absent - up - down extends_documentation_fragment: - vyos.vyos.vyos """ EXAMPLES = """ - name: configure interface vyos.vyos.vyos_interface: name: eth0 description: test-interface - name: remove interface vyos.vyos.vyos_interface: name: eth0 state: absent - name: make interface down vyos.vyos.vyos_interface: name: eth0 enabled: false - name: make interface up vyos.vyos.vyos_interface: name: eth0 enabled: true - name: Configure interface speed, mtu, duplex vyos.vyos.vyos_interface: name: eth5 state: present speed: 100 mtu: 256 duplex: full - name: Set interface using aggregate vyos.vyos.vyos_interface: aggregate: - {name: eth1, description: test-interface-1, speed: 100, duplex: half, mtu: 512} - {name: eth2, description: test-interface-2, speed: 1000, duplex: full, mtu: 256} - name: Disable interface on aggregate net_interface: aggregate: - name: eth1 - name: eth2 enabled: false - name: Delete interface using aggregate net_interface: aggregate: - name: eth1 - name: eth2 state: absent - name: Check lldp neighbors intent arguments vyos.vyos.vyos_interface: name: eth0 neighbors: - port: eth0 host: netdev - name: Config + intent vyos.vyos.vyos_interface: name: eth1 enabled: false state: down """ RETURN = """ commands: description: The list of configuration mode commands to send to the device returned: always, except for the platforms that use Netconf transport to manage the device. type: list sample: - set interfaces ethernet eth0 description "test-interface" - set interfaces ethernet eth0 speed 100 - set interfaces ethernet eth0 mtu 256 - set interfaces ethernet eth0 duplex full """ import re from copy import deepcopy from time import sleep from ansible.module_utils._text import to_text from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.connection import exec_command from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( conditional, remove_default_spec, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( load_config, get_config, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( vyos_argument_spec, ) def search_obj_in_list(name, lst): for o in lst: if o["name"] == name: return o return None def map_obj_to_commands(updates): commands = list() want, have = updates params = ("speed", "description", "duplex", "mtu") for w in want: name = w["name"] disable = w["disable"] state = w["state"] obj_in_have = search_obj_in_list(name, have) set_interface = "set interfaces ethernet " + name delete_interface = "delete interfaces ethernet " + name if state == "absent" and obj_in_have: commands.append(delete_interface) elif state in ("present", "up", "down"): if obj_in_have: for item in params: value = w.get(item) if value and value != obj_in_have.get(item): if item == "description": value = "'" + str(value) + "'" commands.append( set_interface + " " + item + " " + str(value) ) if disable and not obj_in_have.get("disable", False): commands.append(set_interface + " disable") elif not disable and obj_in_have.get("disable", False): commands.append(delete_interface + " disable") else: commands.append(set_interface) for item in params: value = w.get(item) if value: if item == "description": value = "'" + str(value) + "'" commands.append( set_interface + " " + item + " " + str(value) ) if disable: commands.append(set_interface + " disable") return commands def map_config_to_obj(module): data = get_config(module, flags=["| grep interface"]) obj = [] for line in data.split("\n"): if line.startswith("set interfaces ethernet"): match = re.search(r"set interfaces ethernet (\S+)", line, re.M) name = match.group(1) if name: interface = {} for item in obj: if item["name"] == name: interface = item break if not interface: interface = {"name": name} obj.append(interface) match = re.search(r"%s (\S+)" % name, line, re.M) if match: param = match.group(1) if param == "description": match = re.search(r"description (.+)", line, re.M) description = match.group(1).strip("'") interface["description"] = description elif param == "speed": match = re.search(r"speed (\S+)", line, re.M) speed = match.group(1).strip("'") interface["speed"] = speed elif param == "mtu": match = re.search(r"mtu (\S+)", line, re.M) mtu = match.group(1).strip("'") interface["mtu"] = int(mtu) elif param == "duplex": match = re.search(r"duplex (\S+)", line, re.M) duplex = match.group(1).strip("'") interface["duplex"] = duplex elif param.strip("'") == "disable": interface["disable"] = True return obj def map_params_to_obj(module): obj = [] aggregate = module.params.get("aggregate") if aggregate: for item in aggregate: for key in item: if item.get(key) is None: item[key] = module.params[key] d = item.copy() if d["enabled"]: d["disable"] = False else: d["disable"] = True obj.append(d) else: params = { "name": module.params["name"], "description": module.params["description"], "speed": module.params["speed"], "mtu": module.params["mtu"], "duplex": module.params["duplex"], "delay": module.params["delay"], "state": module.params["state"], "neighbors": module.params["neighbors"], } if module.params["enabled"]: params.update({"disable": False}) else: params.update({"disable": True}) obj.append(params) return obj def get_interfaces_data(module, name): command = "show interfaces ethernet %s" % name rc, out, err = exec_command(module, command) return [rc, out, err] def get_lldp_neighbor(module): command = "show lldp neighbors detail" rc, out, err = exec_command(module, command) return [rc, out, err] def check_declarative_intent_params(module, want, result): failed_conditions = [] have_neighbors = None for w in want: want_state = w.get("state") want_neighbors = w.get("neighbors") if want_state not in ("up", "down") and not want_neighbors: continue if result["changed"]: sleep(w["delay"]) command = "show interfaces ethernet %s" % w["name"] rc, out, err = get_interfaces_data(w["name"], module) if rc != 0: module.fail_json( msg=to_text(err, errors="surrogate_then_replace"), command=command, rc=rc, ) if want_state in ("up", "down"): match = re.search(r"%s (\w+)" % "state", out, re.M) have_state = None if match: have_state = match.group(1) if have_state is None or not conditional( want_state, have_state.strip().lower() ): failed_conditions.append("state " + "eq(%s)" % want_state) if want_neighbors: have_host = [] have_port = [] if have_neighbors is None: rc, have_neighbors, err = get_lldp_neighbor(module) if rc != 0: module.fail_json( msg=to_text(err, errors="surrogate_then_replace"), command=command, rc=rc, ) if have_neighbors: lines = have_neighbors.strip().split("Interface: ") for line in lines: field = line.split("\n") if field[0].split(",")[0].strip() == w["name"]: for item in field: if item.strip().startswith("SysName:"): have_host.append(item.split(":")[1].strip()) if item.strip().startswith("PortDescr:"): have_port.append(item.split(":")[1].strip()) for item in want_neighbors: host = item.get("host") port = item.get("port") if host and host not in have_host: failed_conditions.append("host " + host) if port and port not in have_port: failed_conditions.append("port " + port) return failed_conditions def main(): - """ main entry point for module execution - """ + """main entry point for module execution""" neighbors_spec = dict(host=dict(), port=dict()) element_spec = dict( name=dict(), description=dict(), speed=dict(), mtu=dict(type="int"), duplex=dict(choices=["full", "half", "auto"]), enabled=dict(default=True, type="bool"), neighbors=dict(type="list", elements="dict", options=neighbors_spec), delay=dict(default=10, type="int"), state=dict( default="present", choices=["present", "absent", "up", "down"] ), ) aggregate_spec = deepcopy(element_spec) aggregate_spec["name"] = dict(required=True) # remove default in aggregate spec, to handle common arguments remove_default_spec(aggregate_spec) argument_spec = dict( aggregate=dict(type="list", elements="dict", options=aggregate_spec), ) argument_spec.update(element_spec) argument_spec.update(vyos_argument_spec) required_one_of = [["name", "aggregate"]] mutually_exclusive = [["name", "aggregate"]] required_together = [["speed", "duplex"]] module = AnsibleModule( argument_spec=argument_spec, required_one_of=required_one_of, mutually_exclusive=mutually_exclusive, required_together=required_together, supports_check_mode=True, ) warnings = list() result = {"changed": False} if warnings: result["warnings"] = warnings want = map_params_to_obj(module) have = map_config_to_obj(module) commands = map_obj_to_commands((want, have)) result["commands"] = commands if commands: commit = not module.check_mode diff = load_config(module, commands, commit=commit) if diff: if module._diff: result["diff"] = {"prepared": diff} result["changed"] = True failed_conditions = check_declarative_intent_params(module, want, result) if failed_conditions: msg = "One or more conditional statements have not been satisfied" module.fail_json(msg=msg, failed_conditions=failed_conditions) module.exit_json(**result) if __name__ == "__main__": main() diff --git a/plugins/modules/vyos_l3_interface.py b/plugins/modules/vyos_l3_interface.py index 25f24a6..88b5a91 100644 --- a/plugins/modules/vyos_l3_interface.py +++ b/plugins/modules/vyos_l3_interface.py @@ -1,358 +1,357 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2017, Ansible by Red Hat, inc # # This file is part of Ansible by Red Hat # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = """ module: vyos_l3_interface author: Ricardo Carrillo Cruz (@rcarrillocruz) short_description: (deprecated, removed after 2022-06-01) Manage L3 interfaces on VyOS network devices description: - This module provides declarative management of L3 interfaces on VyOS network devices. version_added: 1.0.0 deprecated: alternative: vyos_l3_interfaces why: Updated modules released with more functionality. removed_at_date: '2022-06-01' notes: - Tested against VYOS 1.1.7 options: name: description: - Name of the L3 interface. type: str ipv4: description: - IPv4 of the L3 interface. type: str ipv6: description: - IPv6 of the L3 interface. type: str aggregate: description: List of L3 interfaces definitions type: list elements: dict suboptions: name: description: - Name of the L3 interface. type: str required: True ipv4: description: - IPv4 of the L3 interface. type: str ipv6: description: - IPv6 of the L3 interface. type: str state: description: - State of the L3 interface configuration. type: str choices: - present - absent state: description: - State of the L3 interface configuration. default: present type: str choices: - present - absent extends_documentation_fragment: - vyos.vyos.vyos """ EXAMPLES = """ - name: Set eth0 IPv4 address vyos.vyos.vyos_l3_interface: name: eth0 ipv4: 192.168.0.1/24 - name: Remove eth0 IPv4 address vyos.vyos.vyos_l3_interface: name: eth0 state: absent - name: Set IP addresses on aggregate vyos.vyos.vyos_l3_interface: aggregate: - {name: eth1, ipv4: 192.168.2.10/24} - {name: eth2, ipv4: 192.168.3.10/24, ipv6: fd5d:12c9:2201:1::1/64} - name: Remove IP addresses on aggregate vyos.vyos.vyos_l3_interface: aggregate: - {name: eth1, ipv4: 192.168.2.10/24} - {name: eth2, ipv4: 192.168.3.10/24, ipv6: fd5d:12c9:2201:1::1/64} state: absent """ RETURN = """ commands: description: The list of configuration mode commands to send to the device returned: always, except for the platforms that use Netconf transport to manage the device. type: list sample: - set interfaces ethernet eth0 address '192.168.0.1/24' """ import socket import re from copy import deepcopy from ansible.module_utils.basic import AnsibleModule from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( is_masklen, validate_ip_address, ) from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( remove_default_spec, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( load_config, run_commands, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( vyos_argument_spec, ) def is_ipv4(value): if value: address = value.split("/") if is_masklen(address[1]) and validate_ip_address(address[0]): return True return False def is_ipv6(value): if value: address = value.split("/") if 0 <= int(address[1]) <= 128: try: socket.inet_pton(socket.AF_INET6, address[0]) except socket.error: return False return True return False def search_obj_in_list(name, lst): for o in lst: if o["name"] == name: return o return None def map_obj_to_commands(updates, module): commands = list() want, have = updates for w in want: name = w["name"] ipv4 = w["ipv4"] ipv6 = w["ipv6"] state = w["state"] obj_in_have = search_obj_in_list(name, have) if state == "absent" and obj_in_have: if ( not ipv4 and not ipv6 and (obj_in_have["ipv4"] or obj_in_have["ipv6"]) ): if name == "lo": commands.append("delete interfaces loopback lo address") else: commands.append( "delete interfaces ethernet " + name + " address" ) else: if ipv4 and ipv4 in obj_in_have["ipv4"]: if name == "lo": commands.append( "delete interfaces loopback lo address " + ipv4 ) else: commands.append( "delete interfaces ethernet " + name + " address " + ipv4 ) if ipv6 and ipv6 in obj_in_have["ipv6"]: if name == "lo": commands.append( "delete interfaces loopback lo address " + ipv6 ) else: commands.append( "delete interfaces ethernet " + name + " address " + ipv6 ) elif state == "present" and obj_in_have: if ipv4 and ipv4 not in obj_in_have["ipv4"]: if name == "lo": commands.append( "set interfaces loopback lo address " + ipv4 ) else: commands.append( "set interfaces ethernet " + name + " address " + ipv4 ) if ipv6 and ipv6 not in obj_in_have["ipv6"]: if name == "lo": commands.append( "set interfaces loopback lo address " + ipv6 ) else: commands.append( "set interfaces ethernet " + name + " address " + ipv6 ) return commands def map_config_to_obj(module): obj = [] output = run_commands(module, ["show interfaces"]) lines = re.split(r"\n[e|l]", output[0])[1:] if len(lines) > 0: for line in lines: splitted_line = line.split() if len(splitted_line) > 0: ipv4 = [] ipv6 = [] if splitted_line[0].lower().startswith("th"): name = "e" + splitted_line[0].lower() elif splitted_line[0].lower().startswith("o"): name = "l" + splitted_line[0].lower() for i in splitted_line[1:]: if ("." in i or ":" in i) and "/" in i: value = i.split(r"\n")[0] if is_ipv4(value): ipv4.append(value) elif is_ipv6(value): ipv6.append(value) obj.append({"name": name, "ipv4": ipv4, "ipv6": ipv6}) return obj def map_params_to_obj(module): obj = [] aggregate = module.params.get("aggregate") if aggregate: for item in aggregate: for key in item: if item.get(key) is None: item[key] = module.params[key] obj.append(item.copy()) else: obj.append( { "name": module.params["name"], "ipv4": module.params["ipv4"], "ipv6": module.params["ipv6"], "state": module.params["state"], } ) return obj def main(): - """ main entry point for module execution - """ + """main entry point for module execution""" element_spec = dict( name=dict(), ipv4=dict(), ipv6=dict(), state=dict(default="present", choices=["present", "absent"]), ) aggregate_spec = deepcopy(element_spec) aggregate_spec["name"] = dict(required=True) # remove default in aggregate spec, to handle common arguments remove_default_spec(aggregate_spec) argument_spec = dict( aggregate=dict(type="list", elements="dict", options=aggregate_spec), ) argument_spec.update(element_spec) argument_spec.update(vyos_argument_spec) required_one_of = [["name", "aggregate"]] mutually_exclusive = [["name", "aggregate"]] module = AnsibleModule( argument_spec=argument_spec, required_one_of=required_one_of, mutually_exclusive=mutually_exclusive, supports_check_mode=True, ) warnings = list() result = {"changed": False} if warnings: result["warnings"] = warnings want = map_params_to_obj(module) have = map_config_to_obj(module) commands = map_obj_to_commands((want, have), module) result["commands"] = commands if commands: commit = not module.check_mode load_config(module, commands, commit=commit) result["changed"] = True module.exit_json(**result) if __name__ == "__main__": main() diff --git a/plugins/modules/vyos_linkagg.py b/plugins/modules/vyos_linkagg.py index 766a584..4320dd3 100644 --- a/plugins/modules/vyos_linkagg.py +++ b/plugins/modules/vyos_linkagg.py @@ -1,364 +1,363 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2017, Ansible by Red Hat, inc # # This file is part of Ansible by Red Hat # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = """ module: vyos_linkagg author: Ricardo Carrillo Cruz (@rcarrillocruz) short_description: (deprecated, removed after 2022-06-01) Manage link aggregation groups on VyOS network devices description: - This module provides declarative management of link aggregation groups on VyOS network devices. version_added: 1.0.0 deprecated: alternative: vyos_lag_interfaces why: Updated modules released with more functionality. removed_at_date: '2022-06-01' notes: - Tested against VYOS 1.1.7 options: name: description: - Name of the link aggregation group. type: str mode: description: - Mode of the link aggregation group. default: "802.3ad" choices: - 802.3ad - active-backup - broadcast - round-robin - transmit-load-balance - adaptive-load-balance - xor-hash - "on" type: str members: description: - List of members of the link aggregation group. type: list elements: str aggregate: description: List of link aggregation definitions. type: list elements: dict suboptions: name: description: - Name of the link aggregation group. required: true type: str mode: description: - Mode of the link aggregation group. choices: - 802.3ad - active-backup - broadcast - round-robin - transmit-load-balance - adaptive-load-balance - xor-hash - "on" type: str members: description: - List of members of the link aggregation group. type: list elements: str state: description: - State of the link aggregation group. choices: - present - absent - up - down type: str state: description: - State of the link aggregation group. default: present choices: - present - absent - up - down type: str extends_documentation_fragment: - vyos.vyos.vyos """ EXAMPLES = """ - name: configure link aggregation group vyos.vyos.vyos_linkagg: name: bond0 members: - eth0 - eth1 - name: remove configuration vyos.vyos.vyos_linkagg: name: bond0 state: absent - name: Create aggregate of linkagg definitions vyos.vyos.vyos_linkagg: aggregate: - {name: bond0, members: [eth1]} - {name: bond1, members: [eth2]} - name: Remove aggregate of linkagg definitions vyos.vyos.vyos_linkagg: aggregate: - name: bond0 - name: bond1 state: absent """ RETURN = """ commands: description: The list of configuration mode commands to send to the device returned: always, except for the platforms that use Netconf transport to manage the device. type: list sample: - set interfaces bonding bond0 - set interfaces ethernet eth0 bond-group 'bond0' - set interfaces ethernet eth1 bond-group 'bond0' """ from copy import deepcopy from ansible.module_utils.basic import AnsibleModule from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( remove_default_spec, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( load_config, run_commands, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( vyos_argument_spec, ) def search_obj_in_list(name, lst): for o in lst: if o["name"] == name: return o return None def map_obj_to_commands(updates, module): commands = list() want, have = updates for w in want: name = w["name"] members = w.get("members") or [] mode = w["mode"] if mode == "on": mode = "802.3ad" state = w["state"] obj_in_have = search_obj_in_list(name, have) if state == "absent": if obj_in_have: for m in obj_in_have["members"]: commands.append( "delete interfaces ethernet " + m + " bond-group" ) commands.append("delete interfaces bonding " + name) else: if not obj_in_have: commands.append( "set interfaces bonding " + name + " mode " + mode ) for m in members: commands.append( "set interfaces ethernet " + m + " bond-group " + name ) if state == "down": commands.append( "set interfaces bonding " + name + " disable" ) else: if mode != obj_in_have["mode"]: commands.append( "set interfaces bonding " + name + " mode " + mode ) missing_members = list( set(members) - set(obj_in_have["members"]) ) for m in missing_members: commands.append( "set interfaces ethernet " + m + " bond-group " + name ) if state == "down" and obj_in_have["state"] == "up": commands.append( "set interfaces bonding " + name + " disable" ) elif state == "up" and obj_in_have["state"] == "down": commands.append( "delete interfaces bonding " + name + " disable" ) return commands def map_config_to_obj(module): obj = [] output = run_commands(module, ["show interfaces bonding slaves"]) lines = output[0].splitlines() if len(lines) > 1: for line in lines[1:]: splitted_line = line.split() name = splitted_line[0] mode = splitted_line[1] state = splitted_line[2] if len(splitted_line) > 4: members = splitted_line[4:] else: members = [] obj.append( { "name": name, "mode": mode, "members": members, "state": state, } ) return obj def map_params_to_obj(module): obj = [] aggregate = module.params.get("aggregate") if aggregate: for item in aggregate: for key in item: if item.get(key) is None: item[key] = module.params[key] obj.append(item.copy()) else: obj.append( { "name": module.params["name"], "mode": module.params["mode"], "members": module.params["members"], "state": module.params["state"], } ) return obj def main(): - """ main entry point for module execution - """ + """main entry point for module execution""" element_spec = dict( name=dict(), mode=dict( choices=[ "802.3ad", "active-backup", "broadcast", "round-robin", "transmit-load-balance", "adaptive-load-balance", "xor-hash", "on", ], default="802.3ad", ), members=dict(type="list", elements="str"), state=dict( default="present", choices=["present", "absent", "up", "down"] ), ) aggregate_spec = deepcopy(element_spec) aggregate_spec["name"] = dict(required=True) # remove default in aggregate spec, to handle common arguments remove_default_spec(aggregate_spec) argument_spec = dict( aggregate=dict(type="list", elements="dict", options=aggregate_spec), ) argument_spec.update(element_spec) argument_spec.update(vyos_argument_spec) required_one_of = [["name", "aggregate"]] mutually_exclusive = [["name", "aggregate"]] module = AnsibleModule( argument_spec=argument_spec, required_one_of=required_one_of, mutually_exclusive=mutually_exclusive, supports_check_mode=True, ) warnings = list() result = {"changed": False} if warnings: result["warnings"] = warnings want = map_params_to_obj(module) have = map_config_to_obj(module) commands = map_obj_to_commands((want, have), module) result["commands"] = commands if commands: commit = not module.check_mode load_config(module, commands, commit=commit) result["changed"] = True module.exit_json(**result) if __name__ == "__main__": main() diff --git a/plugins/modules/vyos_lldp.py b/plugins/modules/vyos_lldp.py index feaf841..c1aaf5d 100644 --- a/plugins/modules/vyos_lldp.py +++ b/plugins/modules/vyos_lldp.py @@ -1,144 +1,143 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2017, Ansible by Red Hat, inc # # This file is part of Ansible by Red Hat # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = """ module: vyos_lldp author: Ricardo Carrillo Cruz (@rcarrillocruz) short_description: (deprecated, removed after 2022-06-01) Manage LLDP configuration on VyOS network devices description: - This module provides declarative management of LLDP service on VyOS network devices. version_added: 1.0.0 deprecated: alternative: vyos_lldp_global why: Updated modules released with more functionality. removed_at_date: '2022-06-01' notes: - Tested against VYOS 1.1.7 options: interfaces: description: - Name of the interfaces. type: list elements: str state: description: - State of the link aggregation group. default: present choices: - present - absent - enabled - disabled type: str extends_documentation_fragment: - vyos.vyos.vyos """ EXAMPLES = """ - name: Enable LLDP service vyos.vyos.vyos_lldp: state: present - name: Disable LLDP service vyos.vyos.vyos_lldp: state: absent """ RETURN = """ commands: description: The list of configuration mode commands to send to the device returned: always, except for the platforms that use Netconf transport to manage the device. type: list sample: - set service lldp """ from ansible.module_utils.basic import AnsibleModule from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( get_config, load_config, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( vyos_argument_spec, ) def has_lldp(module): config = get_config(module).splitlines() if "set service 'lldp'" in config or "set service lldp" in config: return True else: return False def main(): - """ main entry point for module execution - """ + """main entry point for module execution""" argument_spec = dict( interfaces=dict(type="list", elements="str"), state=dict( default="present", choices=["present", "absent", "enabled", "disabled"], ), ) argument_spec.update(vyos_argument_spec) module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True ) warnings = list() result = {"changed": False} if warnings: result["warnings"] = warnings HAS_LLDP = has_lldp(module) commands = [] if module.params["state"] == "absent" and HAS_LLDP: commands.append("delete service lldp") elif module.params["state"] == "present" and not HAS_LLDP: commands.append("set service lldp") result["commands"] = commands if commands: commit = not module.check_mode load_config(module, commands, commit=commit) result["changed"] = True module.exit_json(**result) if __name__ == "__main__": main() diff --git a/plugins/modules/vyos_lldp_interface.py b/plugins/modules/vyos_lldp_interface.py index 911add0..b8bf91c 100644 --- a/plugins/modules/vyos_lldp_interface.py +++ b/plugins/modules/vyos_lldp_interface.py @@ -1,281 +1,280 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2017, Ansible by Red Hat, inc # # This file is part of Ansible by Red Hat # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = """ module: vyos_lldp_interface author: Ricardo Carrillo Cruz (@rcarrillocruz) short_description: (deprecated, removed after 2022-06-01) Manage LLDP interfaces configuration on VyOS network devices description: - This module provides declarative management of LLDP interfaces configuration on VyOS network devices. version_added: 1.0.0 deprecated: alternative: vyos_lldp_interfaces why: Updated modules released with more functionality. removed_at_date: '2022-06-01' notes: - Tested against VYOS 1.1.7 options: name: description: - Name of the interface LLDP should be configured on. type: str aggregate: description: List of interfaces LLDP should be configured on. type: list elements: dict suboptions: name: description: - Name of the interface LLDP should be configured on. required: True type: str state: description: - State of the LLDP configuration. choices: - present - absent - enabled - disabled type: str state: description: - State of the LLDP configuration. default: present choices: - present - absent - enabled - disabled type: str extends_documentation_fragment: - vyos.vyos.vyos """ EXAMPLES = """ - name: Enable LLDP on eth1 net_lldp_interface: state: present - name: Enable LLDP on specific interfaces net_lldp_interface: interfaces: - eth1 - eth2 state: present - name: Disable LLDP globally net_lldp_interface: state: disabled - name: Create aggregate of LLDP interface configurations vyos.vyos.vyos_lldp_interface: aggregate: - name: eth1 - name: eth2 state: present - name: Delete aggregate of LLDP interface configurations vyos.vyos.vyos_lldp_interface: aggregate: - name: eth1 - name: eth2 state: absent """ RETURN = """ commands: description: The list of configuration mode commands to send to the device returned: always, except for the platforms that use Netconf transport to manage the device. type: list sample: - set service lldp eth1 - set service lldp eth2 disable """ from copy import deepcopy from ansible.module_utils.basic import AnsibleModule from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( remove_default_spec, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( get_config, load_config, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( vyos_argument_spec, ) def search_obj_in_list(name, lst): for o in lst: if o["name"] == name: return o return None def map_obj_to_commands(updates, module): commands = list() want, have = updates for w in want: name = w["name"] state = w["state"] obj_in_have = search_obj_in_list(name, have) if state == "absent" and obj_in_have: commands.append("delete service lldp interface " + name) elif state in ("present", "enabled"): if not obj_in_have: commands.append("set service lldp interface " + name) elif ( obj_in_have and obj_in_have["state"] == "disabled" and state == "enabled" ): commands.append( "delete service lldp interface " + name + " disable" ) elif state == "disabled": if not obj_in_have: commands.append("set service lldp interface " + name) commands.append( "set service lldp interface " + name + " disable" ) elif obj_in_have and obj_in_have["state"] != "disabled": commands.append( "set service lldp interface " + name + " disable" ) return commands def map_config_to_obj(module): obj = [] config = get_config(module).splitlines() output = [c for c in config if c.startswith("set service lldp interface")] for i in output: splitted_line = i.split() if len(splitted_line) > 5: new_obj = {"name": splitted_line[4]} if splitted_line[5] == "'disable'": new_obj["state"] = "disabled" else: new_obj = {"name": splitted_line[4][1:-1]} new_obj["state"] = "present" obj.append(new_obj) return obj def map_params_to_obj(module): obj = [] aggregate = module.params.get("aggregate") if aggregate: for item in aggregate: for key in item: if item.get(key) is None: item[key] = module.params[key] obj.append(item.copy()) else: obj.append( {"name": module.params["name"], "state": module.params["state"]} ) return obj def main(): - """ main entry point for module execution - """ + """main entry point for module execution""" element_spec = dict( name=dict(), state=dict( default="present", choices=["present", "absent", "enabled", "disabled"], ), ) aggregate_spec = deepcopy(element_spec) aggregate_spec["name"] = dict(required=True) # remove default in aggregate spec, to handle common arguments remove_default_spec(aggregate_spec) argument_spec = dict( aggregate=dict(type="list", elements="dict", options=aggregate_spec), ) argument_spec.update(element_spec) argument_spec.update(vyos_argument_spec) required_one_of = [["name", "aggregate"]] mutually_exclusive = [["name", "aggregate"]] module = AnsibleModule( argument_spec=argument_spec, required_one_of=required_one_of, mutually_exclusive=mutually_exclusive, supports_check_mode=True, ) warnings = list() result = {"changed": False} if warnings: result["warnings"] = warnings want = map_params_to_obj(module) have = map_config_to_obj(module) commands = map_obj_to_commands((want, have), module) result["commands"] = commands if commands: commit = not module.check_mode load_config(module, commands, commit=commit) result["changed"] = True module.exit_json(**result) if __name__ == "__main__": main() diff --git a/plugins/modules/vyos_logging.py b/plugins/modules/vyos_logging.py index fa2f4c1..c7780d8 100644 --- a/plugins/modules/vyos_logging.py +++ b/plugins/modules/vyos_logging.py @@ -1,338 +1,337 @@ #!/usr/bin/python # -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function __metaclass__ = type # (c) 2017, Ansible by Red Hat, inc # # This file is part of Ansible by Red Hat # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # DOCUMENTATION = """ module: vyos_logging author: Trishna Guha (@trishnaguha) short_description: Manage logging on network devices description: - This module provides declarative management of logging on Vyatta Vyos devices. version_added: 1.0.0 notes: - Tested against VyOS 1.1.8 (helium). - This module works with connection C(network_cli). See L(the VyOS OS Platform Options,../network/user_guide/platform_vyos.html). options: dest: description: - Destination of the logs. type: str choices: - console - file - global - host - user name: description: - If value of C(dest) is I(file) it indicates file-name, for I(user) it indicates username and for I(host) indicates the host name to be notified. type: str facility: description: - Set logging facility. type: str level: description: - Set logging severity levels. type: str aggregate: description: List of logging definitions. type: list elements: dict suboptions: dest: description: - Destination of the logs. type: str choices: - console - file - global - host - user name: description: - If value of C(dest) is I(file) it indicates file-name, for I(user) it indicates username and for I(host) indicates the host name to be notified. type: str facility: description: - Set logging facility. type: str level: description: - Set logging severity levels. type: str state: description: - State of the logging configuration. type: str choices: - present - absent state: description: - State of the logging configuration. type: str default: present choices: - present - absent extends_documentation_fragment: - vyos.vyos.vyos """ EXAMPLES = """ - name: configure console logging vyos.vyos.vyos_logging: dest: console facility: all level: crit - name: remove console logging configuration vyos.vyos.vyos_logging: dest: console state: absent - name: configure file logging vyos.vyos.vyos_logging: dest: file name: test facility: local3 level: err - name: Add logging aggregate vyos.vyos.vyos_logging: aggregate: - {dest: file, name: test1, facility: all, level: info} - {dest: file, name: test2, facility: news, level: debug} state: present - name: Remove logging aggregate vyos.vyos.vyos_logging: aggregate: - {dest: console, facility: all, level: info} - {dest: console, facility: daemon, level: warning} - {dest: file, name: test2, facility: news, level: debug} state: absent """ RETURN = """ commands: description: The list of configuration mode commands to send to the device returned: always type: list sample: - set system syslog global facility all level notice """ import re from copy import deepcopy from ansible.module_utils.basic import AnsibleModule from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( remove_default_spec, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( get_config, load_config, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( vyos_argument_spec, ) def spec_to_commands(updates, module): commands = list() want, have = updates for w in want: dest = w["dest"] name = w["name"] facility = w["facility"] level = w["level"] state = w["state"] del w["state"] if state == "absent" and w in have: if w["name"]: commands.append( "delete system syslog {0} {1} facility {2} level {3}".format( dest, name, facility, level ) ) else: commands.append( "delete system syslog {0} facility {1} level {2}".format( dest, facility, level ) ) elif state == "present" and w not in have: if w["name"]: commands.append( "set system syslog {0} {1} facility {2} level {3}".format( dest, name, facility, level ) ) else: commands.append( "set system syslog {0} facility {1} level {2}".format( dest, facility, level ) ) return commands def config_to_dict(module): data = get_config(module) obj = [] for line in data.split("\n"): if line.startswith("set system syslog"): match = re.search(r"set system syslog (\S+)", line, re.M) dest = match.group(1) if dest == "host": match = re.search(r"host (\S+)", line, re.M) name = match.group(1) elif dest == "file": match = re.search(r"file (\S+)", line, re.M) name = match.group(1) elif dest == "user": match = re.search(r"user (\S+)", line, re.M) name = match.group(1) else: name = None if "facility" in line: match = re.search(r"facility (\S+)", line, re.M) facility = match.group(1) if "level" in line: match = re.search(r"level (\S+)", line, re.M) level = match.group(1).strip("'") obj.append( { "dest": dest, "name": name, "facility": facility, "level": level, } ) return obj def map_params_to_obj(module, required_if=None): obj = [] aggregate = module.params.get("aggregate") if aggregate: for item in aggregate: for key in item: if item.get(key) is None: item[key] = module.params[key] module._check_required_if(required_if, item) obj.append(item.copy()) else: if module.params["dest"] not in ("host", "file", "user"): module.params["name"] = None obj.append( { "dest": module.params["dest"], "name": module.params["name"], "facility": module.params["facility"], "level": module.params["level"], "state": module.params["state"], } ) return obj def main(): - """ main entry point for module execution - """ + """main entry point for module execution""" element_spec = dict( dest=dict( type="str", choices=["console", "file", "global", "host", "user"] ), name=dict(type="str"), facility=dict(type="str"), level=dict(type="str"), state=dict(default="present", choices=["present", "absent"]), ) aggregate_spec = deepcopy(element_spec) # remove default in aggregate spec, to handle common arguments remove_default_spec(aggregate_spec) argument_spec = dict( aggregate=dict(type="list", elements="dict", options=aggregate_spec), ) argument_spec.update(element_spec) argument_spec.update(vyos_argument_spec) required_if = [ ("dest", "host", ["name", "facility", "level"]), ("dest", "file", ["name", "facility", "level"]), ("dest", "user", ["name", "facility", "level"]), ("dest", "console", ["facility", "level"]), ("dest", "global", ["facility", "level"]), ] module = AnsibleModule( argument_spec=argument_spec, required_if=required_if, supports_check_mode=True, ) warnings = list() result = {"changed": False} if warnings: result["warnings"] = warnings want = map_params_to_obj(module, required_if=required_if) have = config_to_dict(module) commands = spec_to_commands((want, have), module) result["commands"] = commands if commands: commit = not module.check_mode load_config(module, commands, commit=commit) result["changed"] = True module.exit_json(**result) if __name__ == "__main__": main() diff --git a/plugins/modules/vyos_ping.py b/plugins/modules/vyos_ping.py index 0582c1f..64509fb 100644 --- a/plugins/modules/vyos_ping.py +++ b/plugins/modules/vyos_ping.py @@ -1,262 +1,261 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2017, Ansible by Red Hat, inc # # This file is part of Ansible by Red Hat # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = """ module: vyos_ping short_description: Tests reachability using ping from VyOS network devices description: - Tests reachability using ping from a VyOS device to a remote destination. - Tested against VyOS 1.1.8 (helium) - For a general purpose network module, see the M(net_ping) module. - For Windows targets, use the M(win_ping) module instead. - For targets running Python, use the M(ping) module instead. version_added: 1.0.0 author: - Nilashish Chakraborty (@NilashishC) options: dest: description: - The IP Address or hostname (resolvable by the device) of the remote node. required: true type: str count: description: - Number of packets to send to check reachability. type: int default: 5 source: description: - The source interface or IP Address to use while sending the ping packet(s). type: str ttl: description: - The time-to-live value for the ICMP packet(s). type: int size: description: - Determines the size (in bytes) of the ping packet(s). type: int interval: description: - Determines the interval (in seconds) between consecutive pings. type: int state: description: - Determines if the expected result is success or fail. type: str choices: - absent - present default: present notes: - Tested against VyOS 1.1.8 (helium). - For a general purpose network module, see the M(net_ping) module. - For Windows targets, use the M(win_ping) module instead. - For targets running Python, use the M(ping) module instead. - This module works with connection C(network_cli). See L(the VyOS OS Platform Options,../network/user_guide/platform_vyos.html). extends_documentation_fragment: - vyos.vyos.vyos """ EXAMPLES = """ - name: Test reachability to 10.10.10.10 vyos.vyos.vyos_ping: dest: 10.10.10.10 - name: Test reachability to 10.20.20.20 using source and ttl set vyos.vyos.vyos_ping: dest: 10.20.20.20 source: eth0 ttl: 128 - name: Test unreachability to 10.30.30.30 using interval vyos.vyos.vyos_ping: dest: 10.30.30.30 interval: 3 state: absent - name: Test reachability to 10.40.40.40 setting count and source vyos.vyos.vyos_ping: dest: 10.40.40.40 source: eth1 count: 20 size: 512 """ RETURN = """ commands: description: List of commands sent. returned: always type: list sample: ["ping 10.8.38.44 count 10 interface eth0 ttl 128"] packet_loss: description: Percentage of packets lost. returned: always type: str sample: "0%" packets_rx: description: Packets successfully received. returned: always type: int sample: 20 packets_tx: description: Packets successfully transmitted. returned: always type: int sample: 20 rtt: description: The round trip time (RTT) stats. returned: when ping succeeds type: dict sample: {"avg": 2, "max": 8, "min": 1, "mdev": 24} """ from ansible.module_utils.basic import AnsibleModule from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( run_commands, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( vyos_argument_spec, ) import re def main(): - """ main entry point for module execution - """ + """main entry point for module execution""" argument_spec = dict( count=dict(type="int", default=5), dest=dict(type="str", required=True), source=dict(type="str"), ttl=dict(type="int"), size=dict(type="int"), interval=dict(type="int"), state=dict( type="str", choices=["absent", "present"], default="present" ), ) argument_spec.update(vyos_argument_spec) module = AnsibleModule(argument_spec=argument_spec) count = module.params["count"] dest = module.params["dest"] source = module.params["source"] size = module.params["size"] ttl = module.params["ttl"] interval = module.params["interval"] warnings = list() results = {} if warnings: results["warnings"] = warnings results["commands"] = [ build_ping(dest, count, size, interval, source, ttl) ] ping_results = run_commands(module, commands=results["commands"]) ping_results_list = ping_results[0].split("\n") rtt_info, rate_info = None, None for line in ping_results_list: if line.startswith("rtt"): rtt_info = line if line.startswith("%s packets transmitted" % count): rate_info = line if rtt_info: rtt = parse_rtt(rtt_info) for k, v in rtt.items(): if rtt[k] is not None: rtt[k] = int(v) results["rtt"] = rtt pkt_loss, rx, tx = parse_rate(rate_info) results["packet_loss"] = str(pkt_loss) + "%" results["packets_rx"] = int(rx) results["packets_tx"] = int(tx) validate_results(module, pkt_loss, results) module.exit_json(**results) def build_ping(dest, count, size=None, interval=None, source=None, ttl=None): cmd = "ping {0} count {1}".format(dest, str(count)) if source: cmd += " interface {0}".format(source) if ttl: cmd += " ttl {0}".format(str(ttl)) if size: cmd += " size {0}".format(str(size)) if interval: cmd += " interval {0}".format(str(interval)) return cmd def parse_rate(rate_info): rate_re = re.compile( r"(?P\d+) (?:\w+) (?:\w+), (?P\d+) (?:\w+), (?P\d+)% (?:\w+) (?:\w+), (?:\w+) (?P