Page MenuHomeVyOS Platform

No OneTemporary

Size
479 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/changelogs/fragments/T7943-fix-sanity.yml b/changelogs/fragments/T7943-fix-sanity.yml
new file mode 100644
index 00000000..e5929ae4
--- /dev/null
+++ b/changelogs/fragments/T7943-fix-sanity.yml
@@ -0,0 +1,2 @@
+trivial:
+ - change sanity test ignore file for 2.21
diff --git a/changelogs/fragments/sanity_upstream_fixes.yml b/changelogs/fragments/sanity_upstream_fixes.yml
new file mode 100644
index 00000000..756d36ca
--- /dev/null
+++ b/changelogs/fragments/sanity_upstream_fixes.yml
@@ -0,0 +1,5 @@
+---
+trivial:
+ - procenv.py - ansible-bad-import-from Import PY3 fixed
+ - yaml_helper.py - ansible-bad-import-from Import PY3 fixed
+ - conftest.py - ansible-bad-import-from Import string_types fixed
diff --git a/plugins/module_utils/network/vyos/config/bgp_address_family/bgp_address_family.py b/plugins/module_utils/network/vyos/config/bgp_address_family/bgp_address_family.py
index 0e6bec81..2d6481c2 100644
--- a/plugins/module_utils/network/vyos/config/bgp_address_family/bgp_address_family.py
+++ b/plugins/module_utils/network/vyos/config/bgp_address_family/bgp_address_family.py
@@ -1,407 +1,405 @@
#
# -*- coding: utf-8 -*-
# Copyright 2021 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
"""
The vyos_bgp_address_family config file.
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 its desired end-state is
created.
"""
import re
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_merge,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.bgp_address_family import (
Bgp_address_familyTemplate,
)
-
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.bgp_address_family_14 import (
Bgp_address_familyTemplate14,
)
-
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
+)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
-from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion
-
class Bgp_address_family(ResourceModule):
"""
The vyos_bgp_address_family config class
"""
def __init__(self, module):
super(Bgp_address_family, self).__init__(
empty_fact_val={},
facts_module=Facts(module),
module=module,
resource="bgp_address_family",
tmplt=Bgp_address_familyTemplate(),
)
self.parsers = []
def _validate_template(self):
version = get_os_version(self._module)
if LooseVersion(version) >= LooseVersion("1.4"):
self._tmplt = Bgp_address_familyTemplate14()
else:
self._tmplt = Bgp_address_familyTemplate()
def parse(self):
- """ override parse to check template """
+ """override parse to check template"""
self._validate_template()
return super().parse()
def get_parser(self, name):
"""get_parsers"""
self._validate_template()
return super().get_parser(name)
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
self._validate_template()
if self.state not in ["parsed", "gathered"]:
self.generate_commands()
self.run_commands()
return self.result
def generate_commands(self):
"""Generate configuration commands to send based on
want, have and desired state.
"""
wantd = {}
haved = {}
- if (self.want.get("as_number") == self.have.get("as_number") or
- not self.have or
- LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")):
+ if (
+ self.want.get("as_number") == self.have.get("as_number")
+ or not self.have
+ or LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
+ ):
if self.want:
wantd = {self.want["as_number"]: self.want}
if self.have:
haved = {self.have["as_number"]: self.have}
else:
self._module.fail_json(msg="Only one bgp instance is allowed per device")
# turn all lists of dicts into dicts prior to merge
for entry in wantd, haved:
self._bgp_af_list_to_dict(entry)
# if state is merged, merge want onto have and then compare
if self.state == "merged":
wantd = dict_merge(haved, wantd)
# if state is deleted, empty out wantd and set haved to wantd
if self.state == "deleted":
- for k, have in iteritems(haved):
+ for k, have in haved.items():
self._delete_af(wantd, have)
wantd = {}
if self.state == "overridden":
- for k, have in iteritems(haved):
+ for k, have in haved.items():
if k not in wantd:
self._compare(want={}, have=have)
- for k, want in iteritems(wantd):
+ for k, want in wantd.items():
self._compare(want=want, have=haved.pop(k, {}))
def _compare(self, want, have):
"""Leverages the base class `compare()` method and
populates the list of commands to be run by comparing
the `want` and `have` data with the `parsers` defined
for the Bgp_address_family network resource.
"""
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
self._compare_asn(want, have)
self._compare_af(want, have)
self._compare_neighbors(want, have)
# Do the negation first
command_set = []
for cmd in self.commands:
if cmd not in command_set:
if "delete" in cmd:
command_set.insert(0, cmd)
else:
command_set.append(cmd)
self.commands = command_set
def _compare_af(self, want, have):
waf = want.get("address_family", {})
haf = have.get("address_family", {})
- for name, entry in iteritems(waf):
+ for name, entry in waf.items():
self._compare_lists(
entry,
have=haf.get(name, {}),
as_number=want["as_number"],
afi=name,
)
- for name, entry in iteritems(haf):
+ for name, entry in haf.items():
if name not in waf.keys() and self.state == "replaced":
continue
self._compare_lists({}, entry, as_number=have["as_number"], afi=name)
def _delete_af(self, want, have):
- for as_num, entry in iteritems(want):
- for afi, af_entry in iteritems(entry.get("address_family", {})):
+ for as_num, entry in want.items():
+ for afi, af_entry in entry.get("address_family", {}).items():
if have.get("address_family"):
- for hafi, hentry in iteritems(have["address_family"]):
+ for hafi, hentry in have["address_family"].items():
if hafi == afi:
self.commands.append(
self._tmplt.render(
{
"as_number": as_num,
"address_family": {"afi": afi},
},
"address_family",
True,
),
)
- for neigh, neigh_entry in iteritems(entry.get("neighbors", {})):
+ for neigh, neigh_entry in entry.get("neighbors", {}).items():
if have.get("neighbors"):
- for hneigh, hnentry in iteritems(have["neighbors"]):
+ for hneigh, hnentry in have["neighbors"].items():
if hneigh == neigh:
if not neigh_entry.get("address_family"):
self.commands.append(
self._tmplt.render(
{
"as_number": as_num,
"neighbors": {"neighbor_address": neigh},
},
"neighbors",
True,
),
)
else:
for k in neigh_entry["address_family"].keys():
if (
hnentry.get("address_family")
and k in hnentry["address_family"].keys()
):
self.commands.append(
self._tmplt.render(
{
"as_number": as_num,
"neighbors": {
"neighbor_address": neigh,
"address_family": {"afi": k},
},
},
"neighbors.address_family",
True,
),
)
def _compare_neighbors(self, want, have):
parsers = [
"neighbors.allowas_in",
"neighbors.as_override",
"neighbors.attribute_unchanged.as_path",
"neighbors.attribute_unchanged.med",
"neighbors.attribute_unchanged.next_hop",
"neighbors.capability_dynamic",
"neighbors.capability_orf",
"neighbors.default_originate",
"neighbors.distribute_list",
"neighbors.prefix_list",
"neighbors.filter_list",
"neighbors.maximum_prefix",
"neighbors.nexthop_local",
"neighbors.nexthop_self",
"neighbors.peer_group",
"neighbors.remove_private_as",
"neighbors.route_map",
"neighbors.route_reflector_client",
"neighbors.route_server_client",
"neighbors.soft_reconfiguration",
"neighbors.unsuppress_map",
"neighbors.weight",
]
wneigh = want.get("neighbors", {})
hneigh = have.get("neighbors", {})
- for name, entry in iteritems(wneigh):
- for afi, af_entry in iteritems(entry.get("address_family")):
- for k, val in iteritems(af_entry):
+ for name, entry in wneigh.items():
+ for afi, af_entry in entry.get("address_family").items():
+ for k, val in af_entry.items():
w = {
"as_number": want["as_number"],
"neighbors": {
"neighbor_address": name,
"address_family": {"afi": afi, k: val},
},
}
h = {}
if hneigh.get(name):
if hneigh[name]["address_family"].get(afi):
if hneigh[name]["address_family"][afi].get(k):
h = {
"as_number": want["as_number"],
"neighbors": {
"neighbor_address": name,
"address_family": {
"afi": afi,
k: hneigh[name]["address_family"][afi].pop(k, {}),
},
},
}
self.compare(
parsers=parsers,
want=w,
have=h,
)
- for name, entry in iteritems(hneigh):
+ for name, entry in hneigh.items():
if name not in wneigh.keys():
# remove surplus config for overridden and replaced
if self.state != "replaced":
self.commands.append(
self._tmplt.render(
{
"as_number": have["as_number"],
"neighbors": {"neighbor_address": name},
},
"neighbors",
True,
),
)
continue
- for hafi, haf_entry in iteritems(entry.get("address_family")):
+ for hafi, haf_entry in entry.get("address_family").items():
# remove surplus configs for given neighbor - replace and overridden
- for k, val in iteritems(haf_entry):
+ for k, val in haf_entry.items():
h = {
"as_number": have["as_number"],
"neighbors": {
"neighbor_address": name,
"address_family": {"afi": hafi, k: val},
},
}
self.compare(parsers=parsers, want={}, have=h)
def _compare_lists(self, want, have, as_number, afi):
parsers = [
"aggregate_address",
"network",
"network.backdoor",
"network.path_limit",
"network.route_map",
"redistribute",
"redistribute.metric",
"redistribute.route_map",
"redistribute.table",
]
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
delete_asn = ""
else:
delete_asn = " " + str(as_number)
for attrib in ["redistribute", "networks", "aggregate_address"]:
wdict = want.pop(attrib, {})
hdict = have.pop(attrib, {})
- for key, entry in iteritems(wdict):
+ for key, entry in wdict.items():
if entry != hdict.get(key, {}):
self.compare(
parsers=parsers,
want={
"as_number": as_number,
"address_family": {"afi": afi, attrib: entry},
},
have={
"as_number": as_number,
"address_family": {
"afi": afi,
attrib: hdict.pop(key, {}),
},
},
)
hdict.pop(key, {})
# remove remaining items in have for replaced
if not wdict and hdict:
attrib = re.sub("_", "-", attrib)
attrib = re.sub("networks", "network", attrib)
self.commands.append(
"delete protocols bgp"
+ delete_asn
+ " "
+ "address-family "
+ afi
+ " "
+ attrib,
)
hdict = {}
- for key, entry in iteritems(hdict):
+ for key, entry in hdict.items():
self.compare(
parsers=parsers,
want={},
have={
"as_number": as_number,
"address_family": {"afi": afi, attrib: entry},
},
)
# de-duplicate child commands if parent command is present
- for val in (self.commands):
+ for val in self.commands:
for val2 in self.commands:
if val != val2 and val2.startswith(val):
self.commands.remove(val2)
def _compare_asn(self, want, have):
if want.get("as_number") and not have.get("as_number"):
self.commands.append(
- "set protocols bgp "
- + "system-as "
- + str(want.get("as_number")),
+ "set protocols bgp " + "system-as " + str(want.get("as_number")),
)
def _bgp_af_list_to_dict(self, entry):
- for name, proc in iteritems(entry):
+ for name, proc in entry.items():
if "address_family" in proc:
af_dict = {}
for entry in proc.get("address_family"):
if "networks" in entry:
network_dict = {}
for n_entry in entry.get("networks", []):
network_dict.update({n_entry["prefix"]: n_entry})
entry["networks"] = network_dict
if "aggregate_address" in entry:
agg_dict = {}
for a_entry in entry.get("aggregate_address", []):
agg_dict.update({a_entry["prefix"]: a_entry})
entry["aggregate_address"] = agg_dict
if "redistribute" in entry:
redis_dict = {}
for r_entry in entry.get("redistribute", []):
proto_key = r_entry.get("protocol", "table")
redis_dict.update({proto_key: r_entry})
entry["redistribute"] = redis_dict
for af in proc.get("address_family"):
af_dict.update({af["afi"]: af})
proc["address_family"] = af_dict
if "neighbors" in proc:
neigh_dict = {}
for entry in proc.get("neighbors", []):
neigh_dict.update({entry["neighbor_address"]: entry})
proc["neighbors"] = neigh_dict
self._bgp_af_list_to_dict(proc["neighbors"])
diff --git a/plugins/module_utils/network/vyos/config/bgp_global/bgp_global.py b/plugins/module_utils/network/vyos/config/bgp_global/bgp_global.py
index 91a5af12..075c8fd2 100644
--- a/plugins/module_utils/network/vyos/config/bgp_global/bgp_global.py
+++ b/plugins/module_utils/network/vyos/config/bgp_global/bgp_global.py
@@ -1,417 +1,416 @@
#
# -*- coding: utf-8 -*-
# Copyright 2021 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
"""
The vyos_bgp_global config file.
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 its desired end-state is
created.
"""
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_merge,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.bgp_global import (
Bgp_globalTemplate,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.bgp_global_14 import (
Bgp_globalTemplate14,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Bgp_global(ResourceModule):
"""
The vyos_bgp_global config class
"""
def __init__(self, module):
super(Bgp_global, self).__init__(
empty_fact_val={},
facts_module=Facts(module),
module=module,
resource="bgp_global",
tmplt=Bgp_globalTemplate(),
)
self.parsers = []
def _validate_template(self):
version = get_os_version(self._module)
if LooseVersion(version) >= LooseVersion("1.4"):
self._tmplt = Bgp_globalTemplate14()
else:
self._tmplt = Bgp_globalTemplate()
def parse(self):
"""override parse to check template"""
self._validate_template()
return super().parse()
def get_parser(self, name):
"""get_parsers"""
self._validate_template()
return super().get_parser(name)
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
version = get_os_version(self._module)
if LooseVersion(version) >= LooseVersion("1.4"):
self._asn_mod = ""
else:
self._asn_mod = " " + str(self.have.get("as_number"))
self._validate_template()
if self.state not in ["parsed", "gathered"]:
self.generate_commands()
self.run_commands()
return self.result
def generate_commands(self):
"""Generate configuration commands to send based on
want, have and desired state.
"""
wantd = {}
haved = {}
if (
self.want.get("as_number") == self.have.get("as_number")
or not self.have
or LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
):
if self.want:
wantd = {self.want["as_number"]: self.want}
if self.have:
haved = {self.have["as_number"]: self.have}
else:
self._module.fail_json(msg="Only one bgp instance is allowed per device")
# turn all lists of dicts into dicts prior to merge
for entry in wantd, haved:
self._bgp_global_list_to_dict(entry)
# if state is merged, merge want onto have and then compare
if self.state == "merged":
wantd = dict_merge(haved, wantd)
# if state is deleted, empty out wantd and set haved to wantd
if self.state == "purged":
h_del = {}
- for k, v in iteritems(haved):
+ for k, v in haved.items():
if k in wantd or not wantd:
h_del.update({k: v})
- for num, entry in iteritems(h_del):
+ for num, entry in h_del.items():
self.commands.append(self._tmplt.render({"as_number": num}, "router", True))
wantd = {}
if self.state == "deleted":
self._compare(want={}, have=self.have)
wantd = {}
- for k, want in iteritems(wantd):
+ for k, want in wantd.items():
self._compare(want=want, have=haved.pop(k, {}))
def _compare(self, want, have):
"""Leverages the base class `compare()` method and
populates the list of commands to be run by comparing
the `want` and `have` data with the `parsers` defined
for the Bgp_global network resource.
"""
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
self._compare_asn(want, have)
parsers = ["maximum_paths", "timers"]
self._compare_neighbor(want, have)
self._compare_bgp_params(want, have)
- for name, entry in iteritems(want):
+ for name, entry in want.items():
if name != "as_number":
self.compare(
parsers=parsers,
want={"as_number": want["as_number"], name: entry},
have={
"as_number": want["as_number"],
name: have.pop(name, {}),
},
)
- for name, entry in iteritems(have):
+ for name, entry in have.items():
if name != "as_number":
self.compare(
parsers=parsers,
want={},
have={"as_number": have["as_number"], name: entry},
)
# Do the negation first
command_set = []
for cmd in self.commands:
if cmd not in command_set:
if "delete" in cmd:
command_set.insert(0, cmd)
else:
command_set.append(cmd)
self.commands = command_set
def _compare_neighbor(self, want, have):
parsers = [
"neighbor.advertisement_interval",
"neighbor.allowas_in",
"neighbor.as_override",
"neighbor.attribute_unchanged.as_path",
"neighbor.attribute_unchanged.med",
"neighbor.attribute_unchanged.next_hop",
"neighbor.capability_dynamic",
"neighbor.capability_orf",
"neighbor.default_originate",
"neighbor.description",
"neighbor.disable_capability_negotiation",
"neighbor.disable_connected_check",
"neighbor.disable_send_community",
"neighbor.distribute_list",
"neighbor.ebgp_multihop",
"neighbor.filter_list",
"neighbor.local_as",
"neighbor.maximum_prefix",
"neighbor.nexthop_self",
"neighbor.override_capability",
"neighbor.passive",
"neighbor.password",
"neighbor.peer_group_name",
"neighbor.port",
"neighbor.prefix_list",
"neighbor.remote_as",
"neighbor.remove_private_as",
"neighbor.route_map",
"neighbor.route_reflector_client",
"neighbor.route_server_client",
"neighbor.shutdown",
"neighbor.soft_reconfiguration",
"neighbor.strict_capability_match",
"neighbor.unsuppress_map",
"neighbor.update_source",
"neighbor.weight",
"neighbor.ttl_security",
"neighbor.timers",
]
wneigh = want.pop("neighbor", {})
hneigh = have.pop("neighbor", {})
self._compare_neigh_lists(wneigh, hneigh)
- for name, entry in iteritems(wneigh):
+ for name, entry in wneigh.items():
for k, v in entry.items():
if k == "address":
continue
if hneigh.get(name):
h = {"address": name, k: hneigh[name].pop(k, {})}
else:
h = {}
self.compare(
parsers=parsers,
want={
"as_number": want["as_number"],
"neighbor": {"address": name, k: v},
},
have={"as_number": want["as_number"], "neighbor": h},
)
- for name, entry in iteritems(hneigh):
+ for name, entry in hneigh.items():
if name not in wneigh.keys():
if self._check_af(name):
msg = "Use the _bgp_address_family module to delete the address_family under neighbor {0}, before replacing/deleting the neighbor.".format(
name,
)
self._module.fail_json(msg=msg)
else:
self.commands.append(
"delete protocols bgp" + self._asn_mod + " neighbor " + name,
)
continue
for k, v in entry.items():
self.compare(
parsers=parsers,
want={},
have={
"as_number": have["as_number"],
"neighbor": {"address": name, k: v},
},
)
def _compare_bgp_params(self, want, have):
parsers = [
"bgp_params.always_compare_med",
"bgp_params.bestpath.as_path",
"bgp_params.bestpath.compare_routerid",
"bgp_params.bestpath.med",
"bgp_params.cluster_id",
"bgp_params.confederation",
"bgp_params.dampening_half_life",
"bgp_params.dampening_max_suppress_time",
"bgp_params.dampening_re_use",
"bgp_params.dampening_start_suppress_time",
"bgp_params.default",
"bgp_params.deterministic_med",
"bgp_params.disbale_network_import_check",
"bgp_params.enforce_first_as",
"bgp_params.graceful_restart",
"bgp_params.log_neighbor_changes",
"bgp_params.no_client_to_client_reflection",
"bgp_params.no_fast_external_failover",
"bgp_params.routerid",
"bgp_params.scan_time",
]
wbgp = want.pop("bgp_params", {})
hbgp = have.pop("bgp_params", {})
- for name, entry in iteritems(wbgp):
+ for name, entry in wbgp.items():
if name == "confederation":
if entry != hbgp.pop(name, {}):
self.addcmd(
{
"as_number": want["as_number"],
"bgp_params": {name: entry},
},
"bgp_params.confederation",
False,
)
elif name == "distance":
if entry != hbgp.pop(name, {}):
distance_parsers = [
"bgp_params.distance.global",
"bgp_params.distance.prefix",
]
for distance_type in entry:
self.compare(
parsers=distance_parsers,
want={
"as_number": want["as_number"],
"bgp_params": {name: distance_type},
},
have={
"as_number": want["as_number"],
"bgp_params": {name: hbgp.pop(name, {})},
},
)
else:
self.compare(
parsers=parsers,
want={
"as_number": want["as_number"],
"bgp_params": {name: entry},
},
have={
"as_number": want["as_number"],
"bgp_params": {name: hbgp.pop(name, {})},
},
)
if not wbgp and hbgp:
self.commands.append("delete protocols bgp" + self._asn_mod + " parameters")
hbgp = {}
- for name, entry in iteritems(hbgp):
+ for name, entry in hbgp.items():
if name == "confederation":
self.commands.append(
"delete protocols bgp" + self._asn_mod + " parameters confederation",
)
elif name == "distance":
distance_parsers = [
"bgp_params.distance.global",
"bgp_params.distance.prefix",
]
self.compare(
parsers=distance_parsers,
want={},
have={
"as_number": have["as_number"],
"bgp_params": {name: entry[0]},
},
)
else:
self.compare(
parsers=parsers,
want={},
have={
"as_number": have["as_number"],
"bgp_params": {name: entry},
},
)
def _compare_neigh_lists(self, want, have):
for attrib in [
"distribute_list",
"filter_list",
"prefix_list",
"route_map",
]:
wdict = want.pop(attrib, {})
hdict = have.pop(attrib, {})
- for key, entry in iteritems(wdict):
+ for key, entry in wdict.items():
if entry != hdict.pop(key, {}):
self.addcmd(entry, "neighbor.{0}".format(attrib), False)
# remove remaining items in have for replaced
for entry in hdict.values():
self.addcmd(entry, "neighbor.{0}".format(attrib), True)
def _bgp_global_list_to_dict(self, entry):
- for name, proc in iteritems(entry):
+ for name, proc in entry.items():
if "neighbor" in proc:
neigh_dict = {}
for entry in proc.get("neighbor", []):
neigh_dict.update({entry["address"]: entry})
proc["neighbor"] = neigh_dict
if "network" in proc:
network_dict = {}
for entry in proc.get("network", []):
network_dict.update({entry["address"]: entry})
proc["network"] = network_dict
if "aggregate_address" in proc:
agg_dict = {}
for entry in proc.get("aggregate_address", []):
agg_dict.update({entry["prefix"]: entry})
proc["aggregate_address"] = agg_dict
if "redistribute" in proc:
redis_dict = {}
for entry in proc.get("redistribute", []):
redis_dict.update({entry["protocol"]: entry})
proc["redistribute"] = redis_dict
def _compare_asn(self, want, have):
if want.get("as_number") and not have.get("as_number"):
self.commands.append(
"set protocols bgp " + "system-as" + " " + str(want.get("as_number")),
)
def _check_af(self, neighbor):
af_present = False
if self._connection:
config_lines = self._get_config(self._connection).splitlines()
for line in config_lines:
if neighbor in line:
if "address-family" in line:
af_present = True
return af_present
def _get_config(self, connection):
return connection.get(
'show configuration commands | match "set protocols bgp .*neighbor"',
)
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 0d73d209..ed892462 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,793 +1,794 @@
#
# -*- 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.module_utils.six import iteritems
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 (
remove_empties,
to_list,
)
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 (
- list_diff_want_only,
in_target_not_none,
+ list_diff_want_only,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
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)
: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
: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 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
: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
"""
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
: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
: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):
+ for key, val in want.items():
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):
+ for key, val in have.items():
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):
+ for key, val in w_fg.items():
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 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]):
+ for item, value in h[attr].items():
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]):
+ for item, value in w[attr].items():
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:
h = self.search_attrib_in_have(h_grp, want, "name")
if "afi" in want and want["afi"] == "ipv6":
cmd = self._compute_command(key="group", attr="ipv6-" + attr, opr=opr)
else:
cmd = self._compute_command(key="group", attr=attr, opr=opr)
- for key, val in iteritems(want):
+ for key, val in want.items():
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):
- if commands[-1] == cmd + " " + want["name"] + " " + self._grp_type(attr):
+ if commands[-1] == cmd + " " + want["name"] + " " + self._grp_type(
+ attr,
+ ):
commands.pop()
commands.append(cmd + " " + want["name"])
continue
- if not (h and in_target_not_none(h, key)) and not self._is_grp_del(h, want, "name"):
+ if not (h and in_target_not_none(h, key)) and not self._is_grp_del(
+ h,
+ want,
+ "name",
+ ):
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)],
)
elif not opr and not have:
commands.append(
- cmd
- + " "
- + name
- + " "
- + self._grp_type(type),
+ cmd + " " + name + " " + self._grp_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 = []
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
l_set = ("log", "action", "connection_type", "log_level")
else:
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):
+ for key, val in w.items():
if val and key != "connection_type":
if opr and key in l_set and not (h and self._is_w_same(w, h, key)):
if key == "log" and LooseVersion(
get_os_version(self._module),
) >= LooseVersion("1.4"):
commands.append(
self._form_attr_cmd(
key=attr + " " + w["connection_type"],
attr=key,
opr=opr,
),
)
else:
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:
commands.append(
self._form_attr_cmd(
attr=attr + " " + w["connection_type"],
opr=opr,
),
)
break # delete the whole thing and move on
if (not self._in_target(h, key) or h[key] is None) and (
self._in_target(w, key) and w[key]
):
# delete if not being replaced and value currently exists
commands.append(
self._form_attr_cmd(
attr=attr + " " + w["connection_type"] + " " + key,
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")
if "afi" in w:
afi = w["afi"]
else:
if h and "afi" in h:
afi = h["afi"]
else:
afi = None
afi = None
- for key, val in iteritems(w):
+ for key, val in w.items():
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,
type=afi,
),
)
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,
type=afi,
),
)
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,
type=afi,
),
)
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 and "afi" in w:
afi = w["afi"]
else:
if h and "afi" in h:
afi = h["afi"]
else:
afi = None
if w[attr]:
if h and attr in h.keys():
h_red = h.get(attr) or {}
- for item, value in iteritems(w[attr]):
+ for item, value in w[attr].items():
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,
type=afi,
),
)
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, type=afi))
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, type=None):
"""
This function forms the command for leaf attribute.
:param key: parent key.
:param attr: attribute name
:param value: value
:param opr: True/False.
:param type: AF type of attribute.
:return: generated command.
"""
command = self._compute_command(
key=key,
attr=self._map_attrib(attr, type=type),
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 (
attr
and key != "group"
and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
):
cmd += "global-options "
if key:
cmd += key.replace("_", "-") + " "
if attr:
cmd += attr.replace("_", "-")
if val and opr:
if key == "state_policy" and LooseVersion(get_os_version(self._module)) >= LooseVersion(
"1.4",
):
cmd += ""
else:
cmd += " '" + str(val) + "'"
return cmd.strip()
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 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_rules/firewall_rules.py b/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py
index bb6055b7..436f8b7c 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,1185 +1,1185 @@
#
# -*- 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.module_utils.six import iteritems
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 (
remove_empties,
to_list,
)
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 (
list_diff_want_only,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import (
get_os_version,
load_config,
)
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)
: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
:rtype: A dictionary
:returns: The result from module execution
"""
result = {"changed": False}
warnings = list()
commands = list()
diff = None
try:
self._module.params["comment"]
except KeyError:
comment = []
else:
comment = self._module.params["comment"]
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(deepcopy(existing_firewall_rules_facts)))
if commands and self._module._diff:
commit = not self._module.check_mode
diff = load_config(self._module, commands, commit=commit, comment=comment)
if diff:
result["diff"] = {"prepared": str(diff)}
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 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"]
self._prune_stubs(want)
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
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if self.state in ("merged", "replaced", "overridden", "rendered") and not w:
self._module.fail_json(
msg="value of config parameter must not be empty for state {0}".format(self.state),
)
if self.state == "overridden":
commands.extend(self._state_overridden(w, h))
elif self.state == "deleted":
commands.extend(self._state_deleted(w, h))
elif w:
if self.state == "merged" or self.state == "rendered":
commands.extend(self._state_merged(w, h))
elif self.state == "replaced":
commands.extend(self._state_replaced(w, h))
return commands
def _state_replaced(self, want, have):
"""The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if have:
# Iterate over the afi rule sets we already have.
for h in have:
r_sets = self._get_r_sets(h)
# Iterate over each rule set we already have.
for rs in r_sets:
# In the desired configuration, search for the rule set we
# already have (to be replaced by our desired
# configuration's rule set).
rs_id = self._rs_id(rs, h["afi"])
wanted_rule_set = self.search_r_sets_in_have(want, rs_id, "r_list")
if self._is_same_rs(remove_empties(wanted_rule_set), remove_empties(rs)):
continue
if wanted_rule_set is not None:
# Remove the rules that we already have if the wanted
# rules exist under the same name.
commands.extend(
self._add_r_sets(
h["afi"],
want=rs,
have=wanted_rule_set,
opr=False,
),
)
# Merge the desired configuration into what we already have.
commands.extend(self._state_merged(want, have))
return commands
def _state_overridden(self, want, have):
"""The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if have:
for h in have:
have_r_sets = self._get_r_sets(h)
for rs in have_r_sets:
rs_id = self._rs_id(rs, h["afi"])
w = self.search_r_sets_in_have(want, rs_id, "r_list")
if self._is_same_rs(remove_empties(w), remove_empties(rs)):
continue
else:
commands.append(self._compute_command(rs_id, remove=True))
# Blank out the only rule set that it is removed.
for entry in have:
if entry["afi"] == rs_id["afi"] and rs_id["name"]:
entry["rule_sets"] = [
rule_set
for rule_set in entry["rule_sets"]
if rule_set.get("name") != rs_id["name"]
]
elif entry["afi"] == rs_id["afi"] and rs_id["filter"]:
entry["rule_sets"] = [
rule_set
for rule_set in entry["rule_sets"]
if rule_set.get("filter") != rs_id["filter"]
]
commands.extend(self._state_merged(want, have))
return commands
def _state_merged(self, want, have):
"""The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
for w in want:
r_sets = self._get_r_sets(w)
for rs in r_sets:
rs_id = self._rs_id(rs, w["afi"])
h = self.search_r_sets_in_have(have, rs_id, "r_list")
if self._is_same_rs(remove_empties(h), remove_empties(rs)):
continue
else:
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
: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:
rs_id = self._rs_id(rs, w["afi"])
h = self.search_r_sets_in_have(have, rs_id, "r_list")
if h:
commands.append(self._compute_command(rs_id, remove=True))
elif have:
for h in have:
if h["afi"] == w["afi"]:
commands.append(
self._compute_command(self._rs_id(None, 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(self._rs_id(None, 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", "default_jump_target", "enable_default_log")
h_rs = {}
h_rules = {}
w_rs = deepcopy(remove_empties(want))
w_rules = w_rs.pop("rules", None)
rs_id = self._rs_id(want, afi=afi)
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):
+ for key, val in w_rs.items():
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(rs_id, key, w_rs))
else:
commands.append(self._add_rs_base_attrib(rs_id, 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(rs_id, key, w_rs, opr))
elif not (h_rs and self._in_target(h_rs, key)):
commands.append(self._add_rs_base_attrib(rs_id, key, w_rs, opr))
commands.extend(self._add_rules(rs_id, 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, rs_id, w_rules, h_rules, opr=True):
"""
This function forms the set/delete commands based on the 'opr' type
for rules attributes.
:param rs_id: rule-set identifier.
:param w_rules: desired config.
:param h_rules: target config.
:param opr: True/False.
:return: generated commands list.
"""
commands = []
l_set = (
"ipsec",
"action",
"number",
"protocol",
"fragment",
"disable",
"description",
"jump_target",
)
if w_rules:
for w in w_rules:
cmd = self._compute_command(rs_id, w["number"], opr=opr)
h = self.search_rules_in_have_rs(h_rules, w["number"])
if w != h and self.state == "replaced":
h = {}
- for key, val in iteritems(w):
+ for key, val in w.items():
if val:
if opr and key in l_set and not (h and self._is_w_same(w, h, key)):
if key == "disable":
if not (not val and (not h or key not in h or not h[key])):
commands.append(self._add_r_base_attrib(rs_id, key, w))
else:
commands.append(self._add_r_base_attrib(rs_id, key, w))
elif not opr:
# Note: if you are experiencing sticky configuration on replace
# you may need to add an explicit check for the key here. Anything that
# doesn't have a custom operation is taken care of by the `l_set` check
# below, but I'm not sure how any of the others work.
# It's possible that historically the delete was forced (but now it's
# checked).
if key == "number" and self._is_del(l_set, h):
commands.append(self._add_r_base_attrib(rs_id, key, w, opr=opr))
continue
if (
key == "tcp"
and val
and h
and (key not in h or not h[key] or h[key] != w[key])
):
commands.extend(self._add_tcp(key, w, h, cmd, opr))
if (
key == "state"
and val
and h
and (key not in h or not h[key] or h[key] != w[key])
):
commands.extend(self._add_state(key, w, h, cmd, opr))
if (
key == "icmp"
and val
and h
and (key not in h or not h[key] or h[key] != w[key])
):
commands.extend(self._add_icmp(key, w, h, cmd, opr))
if (
key in ("packet_length", "packet_length_exclude")
and val
and h
and (key not in h or not h[key] or h[key] != w[key])
):
commands.extend(self._add_packet_length(key, w, h, cmd, opr))
elif key == "disable" and val and h and (key not in h or not h[key]):
commands.append(self._add_r_base_attrib(rs_id, key, w, opr=opr))
if (
key in ("inbound_interface", "outbound_interface")
and val
and h
and (key not in h or not h[key] or h[key] != w[key])
):
commands.extend(self._add_interface(key, w, h, cmd, 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(rs_id, key, w, opr=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 == "log":
commands.extend(self._add_log(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))
elif key in ("packet_length", "packet_length_exclude"):
commands.extend(self._add_packet_length(key, w, h, cmd, opr))
elif key in ("inbound_interface", "outbound_interface"):
commands.extend(self._add_interface(key, w, h, cmd, opr))
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]):
+ for item, val in w[attr].items():
if (
opr
and item in l_set
and not (h_state and self._is_w_same(w[attr], h_state, item))
):
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
commands.append(cmd + (" " + attr + " " + item))
else:
commands.append(
cmd + (" " + attr + " " + item + " " + self._bool_to_str(val)),
)
elif not opr and item in l_set and not self._in_target(h_state, item):
commands.append(cmd + (" " + attr + " " + item))
return commands
def _add_log(self, attr, w, h, cmd, opr):
"""
This function forms the command for 'log' 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 = []
if w[attr]:
if h and attr in h.keys():
h_state = h.get(attr) or {}
if (
LooseVersion(get_os_version(self._module)) < LooseVersion("1.4")
and opr
and not (h and self._is_w_same(w, h, attr))
):
commands.append(cmd + " " + attr + " '" + w[attr] + "'")
elif (
LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
and opr
and not (h and self._is_w_same(w, h, attr))
):
commands.append(cmd + " " + attr)
elif not opr and not self._in_target(h_state, w[attr]):
commands.append(cmd + (" " + attr + " '" + w[attr] + "'"))
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]):
+ for item, val in w[attr].items():
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]):
+ for item, val in w[attr].items():
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 LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
param_name = "type-name"
else:
param_name = "type"
if "ipv6" in cmd: # ipv6-name or ipv6
commands.append(cmd + (" " + "icmpv6" + " " + param_name + " " + val))
else:
commands.append(
cmd + (" " + attr + " " + item.replace("_", "-") + " " + val),
)
else:
if "ipv6" in cmd: # ipv6-name or ipv6
commands.append(cmd + (" " + "icmpv6" + " " + item + " " + str(val)))
else:
commands.append(cmd + (" " + attr + " " + item + " " + str(val)))
elif not opr and item in l_set and not self._in_target(h_icmp, item):
commands.append(
cmd + (" " + attr + " " + item.replace("_", "-") + " " + str(val)),
)
return commands
def _add_interface(self, attr, w, h, cmd, opr):
"""
This function forms the commands for 'interface' 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_if = {}
l_set = ("name", "group")
if w[attr]:
if h and attr in h.keys():
h_if = h.get(attr) or {}
- for item, val in iteritems(w[attr]):
+ for item, val in w[attr].items():
if opr and item in l_set and not (h_if and self._is_w_same(w[attr], h_if, item)):
commands.append(
cmd
+ (" " + attr.replace("_", "-") + " " + item.replace("_", "-") + " " + val),
)
elif not opr and item in l_set and not (h_if and self._in_target(h_if, item)):
commands.append(
cmd + (" " + attr.replace("_", "-") + " " + item.replace("_", "-")),
)
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]):
+ for item, val in w[attr].items():
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_1_4(self, attr, w, h, cmd, opr):
"""
This function forms the commands for 'tcp' attributes based on the 'opr'.
Version 1.4+
:param attr: attribute name.
:param w: base config.
:param h: target config.
:param cmd: commands to be prepend.
:return: generated list of commands.
"""
commands = []
have = []
key = "flags"
want = []
if w:
if w.get(attr):
want = w.get(attr).get(key) or []
if h:
if h.get(attr):
have = h.get(attr).get(key) or []
if want:
if opr:
flags = list_diff_want_only(want, have)
for flag in flags:
invert = flag.get("invert", False)
commands.append(
cmd + (" " + attr + " flags " + ("not " if invert else "") + flag["flag"]),
)
elif not opr:
flags = list_diff_want_only(want, have)
for flag in flags:
invert = flag.get("invert", False)
commands.append(
cmd + (" " + attr + " flags " + ("not " if invert else "") + flag["flag"]),
)
return commands
def _add_packet_length(self, attr, w, h, cmd, opr):
"""
This function forms the commands for 'packet_length[_exclude]' attributes based on the 'opr'.
If < 1.4, handle tcp attributes.
:param attr: attribute name.
:param w: base config.
:param h: target config.
:param cmd: commands to be prepend.
:return: generated list of commands.
"""
commands = []
have = []
want = []
if w:
if w.get(attr):
want = w.get(attr) or []
if h:
if h.get(attr):
have = h.get(attr) or []
attr = attr.replace("_", "-")
if want:
if opr:
lengths = list_diff_want_only(want, have)
for l_rec in lengths:
commands.append(cmd + " " + attr + " " + str(l_rec["length"]))
elif not opr:
lengths = list_diff_want_only(want, have)
for l_rec in lengths:
commands.append(cmd + " " + attr + " " + str(l_rec["length"]))
return commands
def _tcp_flags_string(self, flags):
"""
This function forms the tcp flags string.
:param flags: flags list.
:return: flags string or None.
"""
if not flags:
return ""
flag_str = ""
for flag in flags:
this_flag = flag["flag"].upper()
if flag.get("invert", False):
this_flag = "!" + this_flag
if len(flag_str) > 0:
flag_str = ",".join([flag_str, this_flag])
else:
flag_str = this_flag
return flag_str
def _add_tcp(self, attr, w, h, cmd, opr):
"""
This function forms the commands for 'tcp' attributes based on the 'opr'.
If < 1.4, handle tcp attributes.
:param attr: attribute name.
:param w: base config.
:param h: target config.
:param cmd: commands to be prepend.
:return: generated list of commands.
"""
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
return self._add_tcp_1_4(attr, w, h, cmd, opr)
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:
flag_str = self._tcp_flags_string(flags)
if opr and not (h_tcp and flags == h_tcp):
commands.append(cmd + (" " + attr + " " + "flags" + " " + flag_str))
if not opr and not (h_tcp and flags == h_tcp):
commands.append(cmd + (" " + attr + " " + "flags" + " " + flag_str))
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:
h_group = {}
if h and h.get(attr) and key in h[attr].keys():
h_group = h[attr].get(key)
- for item, val in iteritems(group):
+ for item, val in group.items():
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_rules_in_have_rs(self, have_rules, r_number):
"""
This function returns the rule if it is present in target config.
:param have: target config.
:param rs_id: rule-set identifier.
:param r_number: rule-number.
:return: rule.
"""
if have_rules:
key = "number"
for r in have_rules:
if key in r and r[key] == r_number:
return r
return None
def search_r_sets_in_have(self, have, rs_id, type="rule_sets"):
"""
This function returns the rule-set/rule if it is present in target config.
:param have: target config.
:param rs_id: rule-identifier.
:param type: rule_sets if searching a rule_set and r_list if searching from a rule_list.
:return: rule-set/rule.
"""
if "afi" in rs_id:
afi = rs_id["afi"]
else:
afi = None
if rs_id["filter"]:
key = "filter"
w_value = rs_id["filter"]
elif rs_id["name"]:
key = "name"
w_value = rs_id["name"]
else:
raise ValueError("id must be specific to name or filter")
if type not in ("r_list", "rule_sets"):
raise ValueError("type must be rule_sets or r_list")
if have:
if type == "r_list":
for h in have:
if h["afi"] == afi:
r_sets = self._get_r_sets(h)
for rs in r_sets:
if key in rs and rs[key] == w_value:
return rs
else:
# searching a ruleset
for rs in have:
if key in rs and rs[key] == w_value:
return rs
return None
def _get_r_sets(self, item):
"""
This function returns the list of rule-sets.
:param item: config dictionary.
:return: list of rule-sets/rules.
"""
rs_list = []
type = "rule_sets"
r_sets = item[type]
if r_sets:
for rs in r_sets:
rs_list.append(rs)
return rs_list
def _compute_command(
self,
rs_id,
number=None,
attrib=None,
value=None,
remove=False,
opr=True,
):
"""
This function construct the add/delete command based on passed attributes.
:param rs_id: rule-set identifier.
:param number: rule-number.
:param attrib: attribute name.
:param value: value.
:param remove: True if delete command needed to be construct.
:param opr: operation flag.
:return: generated command.
"""
if rs_id["name"] and rs_id["filter"]:
raise ValueError("name and filter cannot be used together")
if remove or not opr:
cmd = "delete firewall " + self._get_fw_type(rs_id["afi"])
else:
cmd = "set firewall " + self._get_fw_type(rs_id["afi"])
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
if rs_id["name"]:
cmd += " name " + rs_id["name"]
elif rs_id["filter"]:
cmd += " " + rs_id["filter"] + " filter"
elif rs_id["name"]:
cmd += " " + rs_id["name"]
if number:
cmd += " rule " + str(number)
if attrib:
if (
LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
and attrib == "enable_default_log"
):
cmd += " " + "default-log"
else:
cmd += " " + attrib.replace("_", "-")
if value and opr and attrib != "enable_default_log" and attrib != "disable":
cmd += " '" + str(value) + "'"
return cmd
def _add_r_base_attrib(self, rs_id, attr, rule, opr=True):
"""
This function forms the command for 'rules' attributes which doesn't
have further sub attributes.
:param rs_id: rule-set identifier.
:param attrib: attribute name
:param rule: rule config dictionary.
:param opr: True/False.
:return: generated command.
"""
if attr == "number":
command = self._compute_command(rs_id, number=rule["number"], opr=opr)
else:
command = self._compute_command(
rs_id=rs_id,
number=rule["number"],
attrib=attr,
value=rule[attr],
opr=opr,
)
return command
def _rs_id(self, have, afi, name=None, filter=None):
"""
This function returns the rule-set identifier based on
the example rule, overriding the components as specified.
:param have: example rule.
:param afi: address type.
:param name: rule-set name.
:param filter: filter name.
:return: rule-set identifier.
"""
identifier = {"name": None, "filter": None}
if afi:
identifier["afi"] = afi
else:
raise ValueError("afi must be provided")
if name:
identifier["name"] = name
return identifier
elif filter:
identifier["filter"] = filter
return identifier
if have:
if "name" in have and have["name"]:
identifier["name"] = have["name"]
return identifier
if "filter" in have and have["filter"]:
identifier["filter"] = have["filter"]
return identifier
# raise ValueError("name or filter must be provided or present in have")
# unless we want a wildcard
return identifier
def _add_rs_base_attrib(self, rs_id, attrib, rule, opr=True):
"""
This function forms the command for 'rule-sets' attributes which don't
have further sub attributes.
:param rs_id: rule-set identifier.
:param attrib: attribute name
:param rule: rule config dictionary.
:param opr: True/False.
:return: generated command.
"""
command = self._compute_command(
rs_id=rs_id,
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.
"""
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
return "ipv6" if afi == "ipv6" else "ipv4"
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 exists 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 _prune_stubs(self, rs):
if isinstance(rs, list):
for item in rs:
self._prune_stubs(item)
elif isinstance(rs, dict):
keys_to_remove = [
key
for key, value in rs.items()
if (
(key == "disable" and value is False)
or (
key == "log"
and value == "disable"
and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
)
or (
key in ["new", "invalid", "related", "established"]
and value is False
and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
)
)
]
for key in keys_to_remove:
del rs[key]
for key in rs:
self._prune_stubs(rs[key])
def _is_same_rs(self, w, rs):
if isinstance(w, dict) and isinstance(rs, dict):
if w.keys() != rs.keys():
return False
for key in w:
if not self._is_same_rs(w[key], rs[key]):
return False
return True
elif isinstance(w, list) and isinstance(rs, list):
try:
+
def comparison(x):
- if 'name' in x:
- return x['name']
- if 'number' in x:
- return x['number']
+ if "name" in x:
+ return x["name"]
+ if "number" in x:
+ return x["number"]
return str(x)
sorted_list1 = sorted(w, key=comparison)
sorted_list2 = sorted(rs, key=comparison)
except TypeError:
return False
if len(sorted_list1) != len(sorted_list2):
return False
return all(self._is_same_rs(x, y) for x, y in zip(sorted_list1, sorted_list2))
else:
return w == rs
diff --git a/plugins/module_utils/network/vyos/config/interfaces/interfaces.py b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py
index a9d9307c..0a55564e 100644
--- a/plugins/module_utils/network/vyos/config/interfaces/interfaces.py
+++ b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py
@@ -1,341 +1,340 @@
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The vyos_interfaces class
It is in this file where the current configuration (as dict)
is compared to the provided configuration (as dict) and the command set
necessary to bring the current configuration to it's desired end-state is
created
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
-from ansible.module_utils.six import iteritems
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 (
dict_diff,
remove_empties,
to_list,
)
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 (
dict_delete,
get_interface_type,
search_obj_in_list,
)
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)
: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
: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 current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params["config"]
have = existing_interfaces_facts
resp = self.set_state(want, have)
return to_list(resp)
def set_state(self, want, have):
"""Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
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"]
enable_state = item["enabled"]
obj_in_have = search_obj_in_list(name, have)
if not obj_in_have:
obj_in_have = {"name": name, "enabled": enable_state}
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
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if have:
commands.extend(self._state_deleted(want, have))
commands.extend(self._state_merged(want, have))
return commands
def _state_overridden(self, want, have):
"""The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
for intf in have:
intf_in_want = search_obj_in_list(intf["name"], want)
if not intf_in_want:
commands.extend(self._state_deleted({"name": intf["name"]}, intf))
for intf in want:
intf_in_have = search_obj_in_list(intf["name"], have)
if not intf_in_have:
intf_in_have = {
"name": intf["name"],
"enabled": intf["enabled"],
}
commands.extend(self._state_replaced(intf, intf_in_have))
return commands
def _state_merged(self, want, have):
"""The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
want_copy = deepcopy(remove_empties(want))
have_copy = deepcopy(have)
want_vifs = want_copy.pop("vifs", [])
have_vifs = have_copy.pop("vifs", [])
updates = dict_diff(have_copy, want_copy)
if updates:
- for key, value in iteritems(updates):
+ for key, value in updates.items():
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):
+ for key, value in vif_updates.items():
commands.append(
self._compute_commands(
key=key,
value=value,
interface=want_copy["name"],
vif=want_vif["vlan_id"],
),
)
return commands
def _state_deleted(self, want, have):
"""The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
want_copy = deepcopy(remove_empties(want))
have_copy = deepcopy(have)
want_vifs = want_copy.pop("vifs", [])
have_vifs = have_copy.pop("vifs", [])
for key in dict_delete(have_copy, want_copy).keys():
if key == "enabled":
continue
commands.append(
self._compute_commands(key=key, interface=want_copy["name"], remove=True),
)
if have_copy["enabled"] is False and not (
"enabled" in want_copy and want_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:
commands.append(
self._compute_commands(
key="",
interface=want_copy["name"],
vif=have_vif["vlan_id"],
remove=True,
),
)
continue
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,
),
)
return commands
def _compute_commands(self, interface, key, vif=None, value=None, remove=False):
interface_type = get_interface_type(interface)
if not interface_type:
self._module.fail_json(
msg="interface {0} is not a valid interface type".format(interface),
)
intf_context = "interfaces {0} {1}".format(interface_type, 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 == "" or key is None:
if not remove:
command = "{0}".format(set_cmd)
else:
command = "{0}".format(del_cmd)
elif 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 6e0c005f..ee709e2a 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,320 +1,319 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The vyos_l3_interfaces class
It is in this file where the current configuration (as dict)
is compared to the provided configuration (as dict) and the command set
necessary to bring the current configuration to it's desired end-state is
created
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
-from ansible.module_utils.six import iteritems
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 (
remove_empties,
to_list,
)
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 (
diff_list_of_dicts,
get_interface_type,
search_obj_in_list,
)
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)
: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
: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 current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params["config"]
have = existing_l3_interfaces_facts
resp = self.set_state(want, have)
return to_list(resp)
def set_state(self, want, have):
"""Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
state = self._module.params["state"]
if state 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
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if have:
commands.extend(self._state_deleted(want, have))
commands.extend(self._state_merged(want, have))
return commands
def _state_overridden(self, want, have):
"""The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
for intf in have:
intf_in_want = search_obj_in_list(intf["name"], want)
if not intf_in_want:
commands.extend(self._state_deleted({"name": intf["name"]}, intf))
for intf in want:
intf_in_have = search_obj_in_list(intf["name"], have)
commands.extend(self._state_replaced(intf, intf_in_have))
return commands
def _state_merged(self, want, have):
"""The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
want_copy = deepcopy(remove_empties(want))
have_copy = deepcopy(remove_empties(have))
want_vifs = want_copy.pop("vifs", [])
have_vifs = have_copy.pop("vifs", [])
for update in self._get_updates(want_copy, have_copy):
- for key, value in iteritems(update):
+ for key, value in update.items():
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):
+ for key, value in update.items():
commands.append(
self._compute_commands(
key=key,
value=value,
interface=want_copy["name"],
vif=want_vif["vlan_id"],
),
)
return commands
def _state_deleted(self, want, have):
"""The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
want_copy = deepcopy(remove_empties(want))
have_copy = deepcopy(have)
want_vifs = want_copy.pop("vifs", [])
have_vifs = have_copy.pop("vifs", [])
for update in self._get_updates(have_copy, want_copy):
- for key, value in iteritems(update):
+ for key, value in update.items():
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):
+ for key, value in update.items():
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 6890fe0c..62270503 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,433 +1,433 @@
# 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.module_utils.six import iteritems
+
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 (
dict_diff,
to_list,
)
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 (
get_lst_diff_for_dicts,
list_diff_have_only,
list_diff_want_only,
search_obj_in_list,
)
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)
: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
: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 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
: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
: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
: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
: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
: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):
+ for key, value in updates.items():
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(
want["name"],
"member interface",
key["member"],
),
)
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(
have["name"],
"member interface",
member["member"],
remove=True,
),
)
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 1dfd25e4..39724f1b 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,307 +1,307 @@
# 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.module_utils.six import iteritems
+
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 (
dict_diff,
to_list,
)
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 (
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",
]
# address intentionally omitted since it's coerced to addresses
params = ["enable", "addresses", "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)
: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
:rtype: A dictionary
:returns: The result from module execution
"""
result = {"changed": False}
warnings = list()
commands = list()
# fix for new name/type
if self._module.params["config"]:
temp_have_address = self._module.params["config"].pop("address", None)
if temp_have_address:
self._module.params["config"]["addresses"] = [temp_have_address]
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 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
: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
: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
: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
: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))
if item == "addresses":
commands.extend(self._update_management_addresses(want, have))
elif have.get(item) and not want.get(item) and item != "enable":
commands.append(Lldp_global.del_cmd + item)
elif have:
if have.get("enable"):
commands.append(self._compute_command(remove=True))
return commands
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 == "addresses":
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)
temp_have_addreses = have.pop("addresses", None)
temp_have_address = have.pop("address", None)
if temp_have_address:
temp_have_addresses = [temp_have_address]
else:
have = {}
temp_want_legacy_protos = want.pop("legacy_protocols", None)
temp_want_addreses = want.pop("addresses", None)
temp_want_address = want.pop("address", None)
if temp_want_address:
temp_want_addresses = [temp_want_address]
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
if have and temp_have_addreses:
have["addresses"] = temp_have_addreses
if not have and temp_want_addreses:
want["addresses"] = temp_want_addreses
commands.extend(self._add_lldp_protocols(want, have))
commands.extend(self._add_management_addresses(want, have))
if updates:
- for key, value in iteritems(updates):
+ for key, value in updates.items():
if value is not None:
if key == "enable":
if value is False:
commands.append(self._compute_command(remove=True))
return commands
else:
commands.append(self._compute_command())
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")
if diff_members:
for key in diff_members:
commands.append(self._compute_command("legacy-protocols", key))
return commands
def _add_management_addresses(self, want, have):
commands = []
diff_members = get_lst_diff_for_dicts(want, have, "addresses")
if diff_members:
for key in diff_members:
commands.append(self._compute_command("management-address", 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 _update_management_addresses(self, want_item, have_item):
commands = []
want_addresses = want_item.get("addresses") or []
have_addresses = have_item.get("addresses") or []
members_diff = list_diff_have_only(want_addresses, have_addresses)
if members_diff:
for member in members_diff:
commands.append(self._compute_command("management-address", 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 2fd6a548..97fcbce5 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,349 +1,348 @@
#
# -*- 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.module_utils.six import iteritems
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 (
dict_diff,
to_list,
)
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 (
is_dict_element_present,
key_value_in_dict,
search_obj_in_list,
)
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)
: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
: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 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
: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
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if have:
commands.extend(self._state_deleted(want, have))
commands.extend(self._state_merged(want, have))
return commands
def _state_overridden(self, want, have):
"""The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
for 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
: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
: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):
+ for key, value in updates.items():
if value:
commands.append(self._compute_command(set_cmd + location_type, key, str(value)))
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):
+ for key, value in have_dict.items():
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))
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 _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/logging_global/logging_global.py b/plugins/module_utils/network/vyos/config/logging_global/logging_global.py
index f94c9195..35a2624a 100644
--- a/plugins/module_utils/network/vyos/config/logging_global/logging_global.py
+++ b/plugins/module_utils/network/vyos/config/logging_global/logging_global.py
@@ -1,223 +1,222 @@
#
# -*- coding: utf-8 -*-
# Copyright 2021 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
"""
The vyos_logging_global config file.
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 its desired end-state is
created.
"""
from copy import deepcopy
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_merge,
get_from_dict,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.logging_global import (
Logging_globalTemplate,
)
class Logging_global(ResourceModule):
"""
The vyos_logging_global config class
"""
def __init__(self, module):
super(Logging_global, self).__init__(
empty_fact_val={},
facts_module=Facts(module),
module=module,
resource="logging_global",
tmplt=Logging_globalTemplate(),
)
self.parsers = [
"console.facilities",
"global_params.archive.file_num",
"global_params.archive.size",
"global_params.marker_interval",
"global_params.preserve_fqdn",
"global_params.facilities",
"files.archive.size",
"files.archive.file_num",
"files",
"hosts.port",
"hosts.facility.protocol", # 1.3 and below
"hosts.protocol",
"hosts",
"users",
]
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
if self.state not in ["parsed", "gathered"]:
self.generate_commands()
self.run_commands()
return self.result
def generate_commands(self):
"""Generate configuration commands to send based on
want, have and desired state.
"""
if self.want:
wantd = self.list_to_dict(self.want, "want")
else:
wantd = dict()
if self.have:
haved = self.list_to_dict(self.have, "have")
else:
haved = dict()
if self.state in ["overridden", "replaced"]:
if wantd != haved:
wantx, havex = self.call_op(wantd, haved, "overridden")
- for k, have in iteritems(havex):
+ for k, have in havex.items():
if k not in wantx:
self._compare(want={}, have=have)
if not self.state == "deleted":
wantd, haved = self.call_op(wantd, haved)
if self.state == "merged":
wantd = dict_merge(haved, wantd)
- for k, want in iteritems(wantd):
+ for k, want in wantd.items():
self._compare(want=want, have=haved.pop(k, {}))
def _compare(self, want, have):
"""Leverages the base class `compare()` method and
populates the list of commands to be run by comparing
the `want` and `have` data with the `parsers` defined
for the Logging_global network resource.
"""
self.compare(parsers=self.parsers, want=want, have=have)
self.handleStates(want=want, have=have)
def operation_rep(self, params):
op_val = dict()
- for k, val in iteritems(params):
+ for k, val in params.items():
if k in ["console", "global_params"]:
mod_val = deepcopy(val)
op_val.update(self.flatten_facility({k: mod_val}))
elif k in ["files", "hosts", "users"]:
- for m, n in iteritems(val):
+ for m, n in val.items():
mod_n = deepcopy(n)
if mod_n.get("archive"):
del mod_n["archive"]
if mod_n.get("facilities"):
del mod_n["facilities"]
if mod_n.get("port"):
del mod_n["port"]
tm = self.flatten_facility({k: {m: mod_n}})
op_val.update(tm)
return op_val
def call_op(self, _want, _have, mode=None):
if mode == "overridden":
w = self.operation_rep(_want)
h = self.operation_rep(_have)
else:
w = self.flatten_facility(_want)
h = self.flatten_facility(_have)
return w, h
def handleStates(self, want=None, have=None):
stateparsers = [
"syslog.state",
"console.state",
"global_params.state",
"global_params.archive.state",
"files.archive.state",
]
for par in stateparsers:
op = get_from_dict(want, par)
if op == "enabled":
self.addcmd(want, par)
elif op == "disabled":
self.addcmd(want, par, True)
break
def flatten_facility(self, param):
temp_param = dict()
- for element, val in iteritems(param):
+ for element, val in param.items():
if element in ["console", "global_params", "syslog"]:
if element != "syslog" and val.get("facilities"):
- for k, v in iteritems(val.get("facilities")):
+ for k, v in val.get("facilities").items():
temp_param[k + element] = {element: {"facilities": v}}
del val["facilities"]
if val:
temp_param[element] = {element: val}
if element in ["files", "hosts", "users"]:
- for k, v in iteritems(val):
+ for k, v in val.items():
if v.get("facilities"):
- for pk, dat in iteritems(v.get("facilities")):
+ for pk, dat in v.get("facilities").items():
temp_param[pk + k] = {
element: {
"facilities": dat,
self.pkey.get(element): v.get(self.pkey.get(element)),
},
}
del v["facilities"]
if len(list(v.keys())) > 1:
temp_param[k] = {element: v}
else:
temp_param[k] = {element: v}
return temp_param
def list_to_dict(self, param, op=None):
updated_param = dict()
if self.state == "deleted":
if op == "have" and param:
self.handleStates({"syslog": {"state": "disabled"}})
updated_param == {}
else:
self.pkey = {
"files": "path",
"hosts": "hostname",
"users": "username",
}
- for element, val in iteritems(param):
+ for element, val in param.items():
if element == "facilities": # only with recursion call
_tem_par = {}
for par in val:
if par.get("facility") and par.get("severity"):
_tem_par.update({par.get("facility") + par.get("severity"): par})
elif par.get("facility") and par.get("protocol"):
_tem_par.update({par.get("facility") + par.get("protocol"): par})
else:
_tem_par.update({par.get("facility"): par})
return _tem_par
elif element in ["console", "global_params", "syslog"]:
if element != "syslog" and val.get("facilities"):
val["facilities"] = self.list_to_dict(val)
updated_param[element] = val
elif element in ["hosts", "users", "files"]:
for v in val:
if v.get("facilities"):
v["facilities"] = self.list_to_dict(v)
if updated_param.get(element):
updated_param[element].update({v.get(self.pkey.get(element)): v})
else:
updated_param[element] = {v.get(self.pkey.get(element)): v}
return updated_param
diff --git a/plugins/module_utils/network/vyos/config/ntp_global/ntp_global.py b/plugins/module_utils/network/vyos/config/ntp_global/ntp_global.py
index 5d294063..06ec1d6b 100644
--- a/plugins/module_utils/network/vyos/config/ntp_global/ntp_global.py
+++ b/plugins/module_utils/network/vyos/config/ntp_global/ntp_global.py
@@ -1,205 +1,204 @@
#
# -*- coding: utf-8 -*-
# Copyright 2021 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
"""
The vyos_ntp config file.
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 its desired end-state is
created.
"""
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_merge,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ntp_global import (
NtpTemplate,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Ntp_global(ResourceModule):
"""
The vyos_ntp config class
"""
def __init__(self, module):
super(Ntp_global, self).__init__(
empty_fact_val={},
facts_module=Facts(module),
module=module,
resource="ntp_global",
tmplt=NtpTemplate(),
)
self.parsers = [
"allow_clients",
"listen_addresses",
"server",
"options",
"allow_clients_delete",
"listen_addresses_delete",
]
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
if self.state not in ["parsed", "gathered"]:
self.generate_commands()
self.run_commands()
return self.result
def generate_commands(self):
"""Generate configuration commands to send based on
want, have and desired state.
"""
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
path = "service"
ac = "allow-client"
else:
path = "system"
ac = "allow-clients"
self._tmplt.set_ntp_path(path)
self._tmplt.set_ntp_ac(ac)
wantd = self._ntp_list_to_dict(self.want)
haved = self._ntp_list_to_dict(self.have)
# if state is merged, merge want onto have and then compare
if self.state == "merged":
wantd = dict_merge(haved, wantd)
# if state is deleted, empty out wantd and set haved to wantd
if self.state == "deleted":
- haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd}
+ haved = {k: v for k, v in haved.items() if k in wantd or not wantd}
wantd = {}
commandlist = self._commandlist(haved)
servernames = self._servernames(haved)
# removing the servername and commandlist from the list after deleting it from haved
# iterate through the top-level items to delete
- for k, have in iteritems(haved):
+ for k, have in haved.items():
if k not in wantd:
- for hk, hval in iteritems(have):
+ for hk, hval in have.items():
if hk == "allow_clients" and hk in commandlist:
self.commands.append(
self._tmplt.render({"": hk}, "allow_clients_delete", True),
)
commandlist.remove(hk)
elif hk == "listen_addresses" and hk in commandlist:
self.commands.append(
self._tmplt.render({"": hk}, "listen_addresses_delete", True),
)
commandlist.remove(hk)
elif hk == "server" and have["server"] in servernames:
self._compareoverride(want={}, have=have)
servernames.remove(have["server"])
# if everything is deleted add the delete command for {path} ntp
# this should be equiv: servernames == [] and commandlist == ["server"]:
if wantd == {} and haved != {}:
self.commands.append(
self._tmplt.render({}, "service_delete", True),
)
# remove existing config for overridden and replaced
# Getting the list of the server names from haved
# to avoid the duplication of overridding/replacing the servers
if self.state in ["overridden", "replaced"]:
commandlist = self._commandlist(haved)
servernames = self._servernames(haved)
- for k, have in iteritems(haved):
+ for k, have in haved.items():
if k not in wantd:
if "server" not in have:
self._compareoverride(want={}, have=have)
# removing the servername from the list after deleting it from haved
elif have["server"] in servernames:
self._compareoverride(want={}, have=have)
servernames.remove(have["server"])
- for k, want in iteritems(wantd):
+ for k, want in wantd.items():
self._compare(want=want, have=haved.pop(k, {}))
def _compare(self, want, have):
"""Leverages the base class `compare()` method and
populates the list of commands to be run by comparing
the `want` and `have` data with the `parsers` defined
for the Ntp network resource.
"""
if "options" in want:
self.compare(parsers="options", want=want, have=have)
else:
self.compare(parsers=self.parsers, want=want, have=have)
def _compareoverride(self, want, have):
# do not delete configuration with options level
- for i, val in iteritems(have):
+ for i, val in have.items():
if i == "options":
pass
else:
self.compare(parsers=i, want={}, have=have)
def _ntp_list_to_dict(self, entry):
servers_dict = {}
- for k, data in iteritems(entry):
+ for k, data in entry.items():
if k == "servers":
for value in data:
if "options" in value:
result = self._serveroptions_list_to_dict(value)
- for res, resvalue in iteritems(result):
+ for res, resvalue in result.items():
servers_dict.update({res: resvalue})
else:
servers_dict.update({value["server"]: value})
else:
for value in data:
servers_dict.update({"ip_" + value: {k: value}})
return servers_dict
def _serveroptions_list_to_dict(self, entry):
serveroptions_dict = {}
- for Opk, Op in iteritems(entry):
+ for Opk, Op in entry.items():
if Opk == "options":
for val in Op:
dict = {}
dict.update({"server": entry["server"]})
dict.update({Opk: val})
serveroptions_dict.update({entry["server"] + "_" + val: dict})
return serveroptions_dict
def _commandlist(self, haved):
commandlist = []
- for k, have in iteritems(haved):
- for ck, cval in iteritems(have):
+ for k, have in haved.items():
+ for ck, cval in have.items():
if ck != "options" and ck not in commandlist:
commandlist.append(ck)
return commandlist
def _servernames(self, haved):
servernames = []
- for k, have in iteritems(haved):
- for sk, sval in iteritems(have):
+ for k, have in haved.items():
+ for sk, sval in have.items():
if sk != "options" and sval not in servernames:
servernames.append(sval)
return servernames
diff --git a/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py b/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py
index 51b47494..837a2ddf 100644
--- a/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py
+++ b/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py
@@ -1,196 +1,194 @@
#
# -*- coding: utf-8 -*-
# Copyright 2020 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
"""
The vyos_ospf_interfaces config file.
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 its desired end-state is
created.
"""
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_merge,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces import (
- Ospf_interfacesTemplate
+ Ospf_interfacesTemplate,
)
-
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces_14 import (
- Ospf_interfacesTemplate14
+ Ospf_interfacesTemplate14,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
)
-
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
-from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion
-
class Ospf_interfaces(ResourceModule):
"""
The vyos_ospf_interfaces config class
"""
def __init__(self, module):
super(Ospf_interfaces, self).__init__(
empty_fact_val={},
facts_module=Facts(module),
module=module,
resource="ospf_interfaces",
tmplt=Ospf_interfacesTemplate(),
)
self.parsers = [
"authentication_password",
"authentication_md5",
"bandwidth",
"cost",
"hello_interval",
"dead_interval",
"mtu_ignore",
"network",
"priority",
"retransmit_interval",
"transmit_delay",
"ifmtu",
"instance",
"passive",
]
def _validate_template(self):
version = get_os_version(self._module)
if LooseVersion(version) >= LooseVersion("1.4"):
self._tmplt = Ospf_interfacesTemplate14()
else:
self._tmplt = Ospf_interfacesTemplate()
def parse(self):
- """ override parse to check template """
+ """override parse to check template"""
self._validate_template()
return super().parse()
def get_parser(self, name):
"""get_parsers"""
self._validate_template()
return super().get_parser(name)
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
self._validate_template()
if self.state not in ["parsed", "gathered"]:
self.generate_commands()
self.run_commands()
return self.result
def generate_commands(self):
"""Generate configuration commands to send based on
want, have and desired state.
"""
wantd = {}
haved = {}
for entry in self.want:
wantd.update({entry["name"]: entry})
for entry in self.have:
haved.update({entry["name"]: entry})
# turn all lists of dicts into dicts prior to merge
for entry in wantd, haved:
self._ospf_int_list_to_dict(entry)
# if state is merged, merge want onto have and then compare
if self.state == "merged":
wantd = dict_merge(haved, wantd)
# if state is deleted, empty out wantd and set haved to wantd
if self.state == "deleted":
h_del = {}
- for k, v in iteritems(haved):
+ for k, v in haved.items():
if k in wantd or not wantd:
h_del.update({k: v})
haved = h_del
have_int = []
- for k, have in iteritems(haved):
+ for k, have in haved.items():
if k in wantd:
have_int.append(k)
self._remove_ospf_int(have)
wantd = {}
if self.state == "overridden":
have_int = []
- for k, have in iteritems(haved):
+ for k, have in haved.items():
if k not in wantd:
have_int.append(k)
self._remove_ospf_int(have)
# remove superfluous config for overridden and deleted
if self.state in ["overridden", "deleted"]:
# removing the interfaces from haved that are already negated
for interface in have_int:
haved.pop(interface)
- for k, have in iteritems(haved):
+ for k, have in haved.items():
if k not in wantd:
self._compare(want={}, have=have)
- for k, want in iteritems(wantd):
+ for k, want in wantd.items():
self._compare(want=want, have=haved.pop(k, {}))
def _remove_ospf_int(self, entry):
int_name = entry.get("name", {})
int_addr = entry.get("address_family", {})
- for k, addr in iteritems(int_addr):
+ for k, addr in int_addr.items():
rem_entry = {"name": int_name, "address_family": {"afi": k}}
self.addcmd(rem_entry, "ip_ospf", True)
def _compare(self, want, have):
"""Leverages the base class `compare()` method and
populates the list of commands to be run by comparing
the `want` and `have` data with the `parsers` defined
for the Ospf_interfaces network resource.
"""
self._compare_addr_family(want=want, have=have)
def _compare_addr_family(self, want, have):
wdict = want.get("address_family", {})
hdict = have.get("address_family", {})
wname = want.get("name")
hname = have.get("name")
- for name, entry in iteritems(wdict):
- for key, param in iteritems(entry):
+ for name, entry in wdict.items():
+ for key, param in entry.items():
w_addr = {"afi": name, key: param}
h_addr = {}
if hdict.get(name):
h_addr = {"afi": name, key: hdict[name].pop(key, {})}
w = {"name": wname, "address_family": w_addr}
h = {"name": hname, "address_family": h_addr}
self.compare(parsers=self.parsers, want=w, have=h)
- for name, entry in iteritems(hdict):
- for key, param in iteritems(entry):
+ for name, entry in hdict.items():
+ for key, param in entry.items():
h_addr = {"afi": name, key: param}
w_addr = {}
w = {"name": wname, "address_family": w_addr}
h = {"name": hname, "address_family": h_addr}
self.compare(parsers=self.parsers, want=w, have=h)
def _ospf_int_list_to_dict(self, entry):
- for name, family in iteritems(entry):
+ for name, family in entry.items():
if "address_family" in family:
addr_dict = {}
for entry in family.get("address_family", []):
addr_dict.update({entry["afi"]: entry})
family["address_family"] = addr_dict
self._ospf_int_list_to_dict(family["address_family"])
diff --git a/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py
index ababc6f1..ed973179 100644
--- a/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py
+++ b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py
@@ -1,820 +1,843 @@
#
# -*- 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.module_utils.six import iteritems
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 (
remove_empties,
to_list,
)
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 (
_bool_to_str,
_in_target,
_is_w_same,
list_diff_want_only,
)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
+)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
-from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion
-
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)
: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
: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 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(remove_empties(want), remove_empties(have))
return to_list(resp)
def set_state(self, w, h):
"""Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if self.state in ("merged", "replaced", "overridden", "rendered") and not w:
self._module.fail_json(
msg="value of config parameter must not be empty for state {0}".format(self.state),
)
if self.state == "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
: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
: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
: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):
+ for key, val in w.items():
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]):
+ for item, value in want[attr].items():
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"]
- elif attr == "passive_interface" and member != "default" and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
- command = command.replace("passive-interface", "interface") + member + " passive"
- elif attr == "passive_interface_exclude" and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
- command = command.replace("passive-interface-exclude", "interface") + member + " passive disable"
+ elif (
+ attr == "passive_interface"
+ and member != "default"
+ and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
+ ):
+ command = (
+ command.replace("passive-interface", "interface") + member + " passive"
+ )
+ elif attr == "passive_interface_exclude" and LooseVersion(
+ get_os_version(self._module),
+ ) >= LooseVersion("1.4"):
+ command = (
+ command.replace("passive-interface-exclude", "interface")
+ + member
+ + " passive disable"
+ )
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:
- if attr == "passive_interface" and member != "default" and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+ if (
+ attr == "passive_interface"
+ and member != "default"
+ and LooseVersion(get_os_version(self._module))
+ >= LooseVersion("1.4")
+ ):
commands.append(cmd + "interface" + " " + member + " passive")
- elif attr == "passive_interface_exclude" and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
- command = command.replace("passive-interface-exclude", "interface") + member + " passive disable"
+ elif attr == "passive_interface_exclude" and LooseVersion(
+ get_os_version(self._module),
+ ) >= LooseVersion("1.4"):
+ command = (
+ command.replace("passive-interface-exclude", "interface")
+ + member
+ + " passive disable"
+ )
else:
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):
+ for key, val in w_item.items():
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):
+ for key, val in w_item.items():
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]):
+ for item, val in w[key].items():
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):
+ for key, val in w_area.items():
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]):
+ for item, val in w_type[key].items():
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 2538a6f5..30d30571 100644
--- a/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py
+++ b/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py
@@ -1,451 +1,450 @@
#
# -*- 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.module_utils.six import iteritems
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 (
remove_empties,
search_obj_in_list,
to_list,
)
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 (
_bool_to_str,
_in_target,
_is_w_same,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
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)
: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
: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 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
: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
: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
: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
: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):
+ for key, val in w.items():
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]):
+ for item, value in want[attr].items():
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",
"interface": "name",
}
leaf_dict = {
"redistribute": ("route_map", "route_type"),
"range": ("address", "advertise", "not_advertise"),
"interface": ("name"),
}
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):
+ for key, val in w_item.items():
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 in ["route_type", "name"] or (
key == "address"
and "advertise" not in w_item
and "not-advertise" not in w_item
):
if not val:
cmd = cmd.replace("set", "delete")
if (
LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
and attr == "interface"
):
words = cmd.split()
cmd14_list = []
for word in words:
cmd14_list.append(word)
if word == "ospfv3":
cmd14_list.append(attr + " " + str(val))
commands.append(" ".join(cmd14_list))
else:
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", "name"):
if (
LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
and attr == "interface"
):
words = cmd.split()
cmd14_list = []
for word in words:
cmd14_list.append(word)
if word == "ospfv3":
cmd14_list.append(attr + " " + str(val))
commands.append(" ".join(cmd14_list))
else:
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):
+ for key, val in w_area.items():
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),
)
elif key == "interface":
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/prefix_lists/prefix_lists.py b/plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py
index 9da27c15..9b703399 100644
--- a/plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py
+++ b/plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py
@@ -1,175 +1,174 @@
#
# -*- coding: utf-8 -*-
# Copyright 2021 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
"""
The vyos_prefix_lists config file.
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 its desired end-state is
created.
"""
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_merge,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.prefix_lists import (
Prefix_listsTemplate,
)
class Prefix_lists(ResourceModule):
"""
The vyos_prefix_lists config class
"""
def __init__(self, module):
super(Prefix_lists, self).__init__(
empty_fact_val=[],
facts_module=Facts(module),
module=module,
resource="prefix_lists",
tmplt=Prefix_listsTemplate(),
)
self.plist_parsers = [
"name",
"description",
]
self.entries_parsers = [
"sequence",
"action",
"rule_description",
"ge",
"le",
"prefix",
]
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
if self.state not in ["parsed", "gathered"]:
self.generate_commands()
self.run_commands()
return self.result
def generate_commands(self):
"""Generate configuration commands to send based on
want, have and desired state.
"""
wantd = {entry["afi"]: entry for entry in self.want}
haved = {entry["afi"]: entry for entry in self.have}
self._prefix_list_list_to_dict(wantd)
self._prefix_list_list_to_dict(haved)
# if state is merged, merge want onto have and then compare
if self.state == "merged":
wantd = dict_merge(haved, wantd)
# if state is deleted, empty out wantd and set haved to wantd
if self.state == "deleted":
- haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd}
- for key, hvalue in iteritems(haved):
+ haved = {k: v for k, v in haved.items() if k in wantd or not wantd}
+ for key, hvalue in haved.items():
wvalue = wantd.pop(key, {})
if wvalue:
wplists = wvalue.get("prefix_lists", {})
hplists = hvalue.get("prefix_lists", {})
hvalue["prefix_lists"] = {
- k: v for k, v in iteritems(hplists) if k in wplists or not wplists
+ k: v for k, v in hplists.items() if k in wplists or not wplists
}
# remove superfluous config for overridden and deleted
if self.state in ["overridden", "deleted"]:
- for k, have in iteritems(haved):
+ for k, have in haved.items():
if k not in wantd:
self._compare(want={}, have=have)
- for k, want in iteritems(wantd):
+ for k, want in wantd.items():
self._compare(want=want, have=haved.pop(k, {}))
def _compare(self, want, have):
"""Leverages the base class `compare()` method and
populates the list of commands to be run by comparing
the `want` and `have` data with the `parsers` defined
for the Prefix_lists network resource.
"""
wplists = want.get("prefix_lists", {})
hplists = have.get("prefix_lists", {})
self._compare_plists(want=wplists, have=hplists)
if self.state in ["overridden", "deleted"]:
# remove remaining prefix lists
for h in hplists.values():
self.commands.append(
"delete policy prefix-{0} {1}".format(
"list" if h["afi"] == "ipv4" else "list6",
h["name"],
),
)
def _compare_plists(self, want, have):
- for wk, wentry in iteritems(want):
+ for wk, wentry in want.items():
hentry = have.pop(wk, {})
# parser list for name and descriptions
self.compare(
parsers=self.plist_parsers,
want=wentry,
have=hentry,
)
wplrules = wentry.get("entries", {})
hplrules = hentry.get("entries", {})
self._compare_rules(want=wplrules, have=hplrules)
def _compare_rules(self, want, have):
- for wr, wrule in iteritems(want):
+ for wr, wrule in want.items():
hrule = have.pop(wr, {})
# parser list for entries
self.compare(
parsers=self.entries_parsers,
want=wrule,
have=hrule,
)
# remove remaining entries
for hr in have.values():
self.commands.append(
"delete policy prefix-{0} {1} rule {2}".format(
"list" if hr["afi"] == "ipv4" else "list6",
hr["name"],
hr["sequence"],
),
)
def _prefix_list_list_to_dict(self, entry):
- for afi, value in iteritems(entry):
+ for afi, value in entry.items():
if "prefix_lists" in value:
for pl in value["prefix_lists"]:
pl.update({"afi": afi})
if "entries" in pl:
for entry in pl["entries"]:
entry.update({"afi": afi, "name": pl["name"]})
pl["entries"] = {x["sequence"]: x for x in pl["entries"]}
value["prefix_lists"] = {entry["name"]: entry for entry in value["prefix_lists"]}
diff --git a/plugins/module_utils/network/vyos/config/route_maps/route_maps.py b/plugins/module_utils/network/vyos/config/route_maps/route_maps.py
index 9692a253..dca99d1c 100644
--- a/plugins/module_utils/network/vyos/config/route_maps/route_maps.py
+++ b/plugins/module_utils/network/vyos/config/route_maps/route_maps.py
@@ -1,184 +1,183 @@
#
# -*- coding: utf-8 -*-
# Copyright 2021 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
"""
The vyos_route_maps config file.
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 its desired end-state is
created.
"""
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_merge,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.route_maps import (
Route_mapsTemplate,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.route_maps_14 import (
Route_mapsTemplate14,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Route_maps(ResourceModule):
"""
The vyos_route_maps config class
"""
def __init__(self, module):
super(Route_maps, self).__init__(
empty_fact_val=[],
facts_module=Facts(module),
module=module,
resource="route_maps",
tmplt=Route_mapsTemplate(),
)
self.parsers = [
"call",
"description",
"action",
"continue_sequence",
"set_aggregator_ip",
"set_aggregator_as",
"set_as_path_exclude",
"set_as_path_prepend",
"set_atomic_aggregate",
"set_bgp_extcommunity_rt",
"set_extcommunity_rt",
"set_extcommunity_soo",
"set_extcommunity_bandwidth",
"set_extcommunity_bandwidth_non_transitive",
"set_ip_next_hop",
"set_ipv6_next_hop",
"set_large_community",
"set_local_preference",
"set_metric",
"set_metric_type",
"set_origin",
"set_originator_id",
"set_src",
"set_tag",
"set_weight",
"set_table",
"set_comm_list",
"set_comm_list_delete",
"set_community",
"match_as_path",
"match_community_community_list",
"match_community_exact_match",
"match_extcommunity",
"match_interface",
"match_large_community_large_community_list",
"match_metric",
"match_origin",
"match_peer",
"match_ip_address",
"match_ip_next_hop",
"match_ip_route_source",
"on_match_goto",
"on_match_next",
"match_ipv6_address",
"match_ipv6_nexthop",
"match_protocol",
"match_rpki",
]
def _validate_template(self):
version = get_os_version(self._module)
if LooseVersion(version) >= LooseVersion("1.4"):
self._tmplt = Route_mapsTemplate14()
else:
self._tmplt = Route_mapsTemplate()
def parse(self):
"""override parse to check template"""
self._validate_template()
return super().parse()
def get_parser(self, name):
"""get_parsers"""
self._validate_template()
return super().get_parser(name)
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
self._validate_template()
if self.state not in ["parsed", "gathered"]:
self.generate_commands()
self.run_commands()
return self.result
def generate_commands(self):
"""Generate configuration commands to send based on
want, have and desired state.
"""
wantd = self._route_maps_list_to_dict(self.want)
haved = self._route_maps_list_to_dict(self.have)
# if state is merged, merge want onto have and then compare
if self.state == "merged":
wantd = dict_merge(haved, wantd)
# if state is deleted, empty out wantd and set haved to wantd
if self.state == "deleted":
- haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd}
+ haved = {k: v for k, v in haved.items() if k in wantd or not wantd}
wantd = {}
# remove superfluous config for overridden and deleted
if self.state in ["overridden", "deleted"]:
- for k, have in iteritems(haved):
+ for k, have in haved.items():
if k not in wantd:
self.commands.append(self._tmplt.render({"route_map": k}, "route_map", True))
- for wk, want in iteritems(wantd):
+ for wk, want in wantd.items():
self._compare(want=want, have=haved.pop(wk, {}))
def _compare(self, want, have):
"""Leverages the base class `compare()` method and
populates the list of commands to be run by comparing
the `want` and `have` data with the `parsers` defined
for the Route_maps network resource.
"""
w_entries = want.get("entries", {})
h_entries = have.get("entries", {})
self._compare_entries(want=w_entries, have=h_entries)
def _compare_entries(self, want, have):
- for wk, wentry in iteritems(want):
+ for wk, wentry in want.items():
hentry = have.pop(wk, {})
self.compare(parsers=self.parsers, want=wentry, have=hentry)
def _route_maps_list_to_dict(self, entry):
entry = {x["route_map"]: x for x in entry}
- for rmap, data in iteritems(entry):
+ for rmap, data in entry.items():
if "entries" in data:
for x in data["entries"]:
x.update({"route_map": rmap})
data["entries"] = {
(rmap, entry.get("sequence")): entry for entry in data["entries"]
}
return entry
diff --git a/plugins/module_utils/network/vyos/config/snmp_server/snmp_server.py b/plugins/module_utils/network/vyos/config/snmp_server/snmp_server.py
index 9497d7fa..4bc5ddc4 100644
--- a/plugins/module_utils/network/vyos/config/snmp_server/snmp_server.py
+++ b/plugins/module_utils/network/vyos/config/snmp_server/snmp_server.py
@@ -1,306 +1,305 @@
#
# -*- coding: utf-8 -*-
# Copyright 2022 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
"""
The vyos_snmp_server config file.
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 its desired end-state is
created.
"""
import re
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_merge,
get_from_dict,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.snmp_server import (
Snmp_serverTemplate,
)
class Snmp_server(ResourceModule):
"""
The vyos_snmp_server config class
"""
def __init__(self, module):
super(Snmp_server, self).__init__(
empty_fact_val={},
facts_module=Facts(module),
module=module,
resource="snmp_server",
tmplt=Snmp_serverTemplate(),
)
self.parsers = [
"contact",
"description",
"location",
"smux_peer",
"trap_source",
"trap_target",
]
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
if self.state not in ["parsed", "gathered"]:
self.generate_commands()
self.run_commands()
return self.result
def generate_commands(self):
"""Generate configuration commands to send based on
want, have and desired state.
"""
wantd = self._snmp_server_list_to_dict(self.want)
haved = self._snmp_server_list_to_dict(self.have)
# if state is merged, merge want onto have and then compare
if self.state == "merged":
wantd = dict_merge(haved, wantd)
# if state is deleted, empty out wantd and set haved to wantd
if self.state == "deleted":
if haved:
self.commands.append("delete service snmp")
if self.state != "deleted":
self._compare(want=wantd, have=haved)
if self.state not in ["merged", "deleted"]:
self._move_negate_commands()
def _move_negate_commands(self):
command_set = []
for cmd in self.commands:
if re.search("delete service snmp", cmd):
command_set.insert(0, cmd)
else:
command_set.append(cmd)
self.commands = command_set
def _compare(self, want, have):
"""Leverages the base class `compare()` method and
populates the list of commands to be run by comparing
the `want` and `have` data with the `parsers` defined
for the Snmp_server network resource.
"""
self._compare_lists(want, have)
self._compare_snmp_v3(want, have)
- for key, value in iteritems(want):
+ for key, value in want.items():
self.compare(
parsers=self.parsers,
want={key: value},
have={key: have.pop(key, "")},
)
- for key, entry in iteritems(have):
+ for key, entry in have.items():
if entry:
self.compare(parsers=self.parsers, want={}, have={key: entry})
def _compare_lists(self, want, have):
parsers = [
"communities",
"listen_addresses",
]
for attrib in parsers:
wdict = get_from_dict(want, attrib) or {}
hdict = get_from_dict(have, attrib) or {}
- for key, entry in iteritems(wdict):
+ for key, entry in wdict.items():
# self.addcmd(entry, attrib, False)
if attrib == "communities":
- for k, v in iteritems(entry):
+ for k, v in entry.items():
if k in ["clients", "networks"]:
v.sort()
h = {}
if k != "name":
if hdict.get(key):
h = {
"communities": {
k: hdict[key].pop(k, ""),
"name": key,
},
}
self.compare(
parsers="communities",
want={"communities": {k: v, "name": key}},
have=h,
)
else:
self.compare(
parsers="listen_addresses",
want={"listen_addresses": entry},
have={"listen_addresses": hdict.pop(key, {})},
)
have.pop(attrib, {})
# remove remaining items in have for replaced
- for key, entry in iteritems(hdict):
+ for key, entry in hdict.items():
if attrib == "communities":
- for k, v in iteritems(entry):
+ for k, v in entry.items():
if k != "name":
self.compare(
parsers="communities",
have={"communities": {k: v, "name": key}},
want={},
)
else:
self.compare(parsers=parsers, want={}, have={attrib: entry})
def _compare_snmp_v3(self, want, have):
parsers = [
"snmp_v3.groups",
"snmp_v3.trap_targets.port",
"snmp_v3.trap_targets.protocol",
"snmp_v3.trap_targets.type",
"snmp_v3.trap_targets.user",
"snmp_v3.users.group",
"snmp_v3.users.mode",
"snmp_v3.views",
"snmp_v3.engine_id",
]
attribute_dict = {
"groups": "group",
"trap_targets": "address",
"users": "user",
"views": "view",
}
wdict = get_from_dict(want, "snmp_v3") or {}
hdict = get_from_dict(have, "snmp_v3") or {}
for attrib in attribute_dict.keys():
wattrib = get_from_dict(wdict, attrib) or {}
hattrib = get_from_dict(hdict, attrib) or {}
- for key, entry in iteritems(wattrib):
+ for key, entry in wattrib.items():
self._compare_snmp_v3_auth_privacy(entry, hattrib.get(key, {}), attrib)
- for k, v in iteritems(entry):
+ for k, v in entry.items():
if k != attribute_dict[attrib]:
h = {}
if hattrib.get(key):
h = {
"snmp_v3": {
attrib: {
k: hattrib[key].pop(k, ""),
attribute_dict[attrib]: hattrib[key][
attribute_dict[attrib]
],
},
},
}
self.compare(
parsers=parsers,
want={
"snmp_v3": {
attrib: {
k: v,
attribute_dict[attrib]: entry[attribute_dict[attrib]],
},
},
},
have=h,
)
# remove remaining items in have for replaced
- for key, entry in iteritems(hattrib):
+ for key, entry in hattrib.items():
self._compare_snmp_v3_auth_privacy({}, entry, attrib)
self.compare(parsers=parsers, want={}, have={"snmp_v3": {attrib: entry}})
hdict.pop(attrib, {})
- for key, entry in iteritems(wdict):
+ for key, entry in wdict.items():
# self.addcmd(entry, attrib, False)
self.compare(
parsers="snmp_v3.engine_id",
want={"snmp_v3": {key: entry}},
have={"snmp_v3": {key: hdict.pop(key, {})}},
)
# remove remaining items in have for replaced
- for key, entry in iteritems(hdict):
+ for key, entry in hdict.items():
self.compare(parsers=parsers, want={}, have={"snmp_v3": {key: entry}})
def _compare_snmp_v3_auth_privacy(self, wattrib, hattrib, attrib):
parsers = [
"snmp_v3.trap_targets.authentication",
"snmp_v3.trap_targets.privacy",
"snmp_v3.users.authentication",
"snmp_v3.users.privacy",
]
if attrib in ["trap_targets", "users"]:
if attrib == "users":
primary_key = "user"
else:
primary_key = "address"
- for key, entry in iteritems(wattrib):
+ for key, entry in wattrib.items():
if key != primary_key and key in ["authentication", "privacy"]:
self.compare(
parsers=parsers,
want={
"snmp_v3": {
attrib: {
key: entry,
primary_key: wattrib[primary_key],
},
},
},
have={
"snmp_v3": {
attrib: {
key: hattrib.pop(key, {}),
primary_key: wattrib[primary_key],
},
},
},
)
- for key, entry in iteritems(hattrib):
+ for key, entry in hattrib.items():
if key != primary_key and key in ["authentication", "privacy"]:
self.compare(
parsers=parsers,
want={},
have={
"snmp_v3": {
attrib: {
key: entry,
primary_key: hattrib[primary_key],
},
},
},
)
def _snmp_server_list_to_dict(self, entry):
param_dict = {
"communities": "name",
"listen_addresses": "address",
}
v3_param_dict = {
"groups": "group",
"users": "user",
"views": "view",
"trap_targets": "address",
}
- for k, v in iteritems(param_dict):
+ for k, v in param_dict.items():
if k in entry:
a_dict = {}
for el in entry[k]:
a_dict.update({el[v]: el})
entry[k] = a_dict
- for k, v in iteritems(v3_param_dict):
+ for k, v in v3_param_dict.items():
if entry.get("snmp_v3") and k in entry.get("snmp_v3"):
a_dict = {}
for el in entry["snmp_v3"][k]:
a_dict.update({el[v]: el})
entry["snmp_v3"][k] = a_dict
return entry
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 8451e7da..da8f8e3d 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,605 +1,604 @@
#
# -*- 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.module_utils.six import iteritems
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 (
dict_diff,
remove_empties,
to_list,
)
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 (
dict_delete,
get_lst_diff_for_dicts,
get_lst_same_for_dicts,
get_route_type,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
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)
: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
: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 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
: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
: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):
+ for key, value in want.items():
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
: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
: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
: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):
+ for key, value in want.items():
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):
+ for attrib, value in updates.items():
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"] + " " + "distance",
value=str(hop[element]),
opr=opr,
),
)
elif element == "interface" and LooseVersion(
get_os_version(self._module),
) < LooseVersion("1.4"):
commands.append(
self._compute_command(
dest=want["dest"],
key="next-hop-interface",
value=hop[element],
opr=opr,
).replace("route", "interface-route"),
)
elif element == "interface" and LooseVersion(
get_os_version(self._module),
) >= LooseVersion("1.4"):
commands.append(
self._compute_command(
dest=want["dest"],
key="interface",
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):
+ for attrib, value in updates.items():
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"] + " " + "distance",
value=str(hop[element]),
remove=True,
),
)
elif element == "interface" and LooseVersion(
get_os_version(self._module),
) < LooseVersion("1.4"):
commands.append(
self._compute_command(
dest=want["dest"],
key="next-hop-interface",
value=hop[element],
remove=True,
).replace("route", "interface-route"),
)
elif element == "interface" and LooseVersion(
get_os_version(self._module),
) >= LooseVersion("1.4"):
commands.append(
self._compute_command(
dest=want["dest"],
key="interface",
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):
+ for key, value in updates.items():
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/l3_interfaces/l3_interfaces.py b/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py
index 7d4d1a08..d62ea9b8 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,146 +1,145 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The vyos l3_interfaces fact class
It is in this file the configuration is collected from the device
for a given resource, parsed, and the facts tree is populated
based on the configuration.
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import re
from copy import deepcopy
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.l3_interfaces.l3_interfaces import (
L3_interfacesArgs,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import (
get_ip_address_version,
)
class L3_interfacesFacts(object):
"""The vyos l3_interfaces fact class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = L3_interfacesArgs.argument_spec
spec = deepcopy(self.argument_spec)
if subspec:
if options:
facts_argument_spec = spec[subspec][options]
else:
facts_argument_spec = spec[subspec]
else:
facts_argument_spec = spec
self.generated_spec = utils.generate_dict(facts_argument_spec)
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for l3_interfaces
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
if not data:
data = connection.get_config()
# operate on a collection of resource x
objs = []
interface_names = re.findall(
r"set interfaces (?:ethernet|bonding|bridge|dummy|tunnel|vti|loopback|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})
elif item == "no-default-link-local":
config["ipv6"].append({"address": item})
elif item == "autoconf":
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):
+ for key, value in config.items():
if value == []:
config[key] = None
return utils.remove_empties(config)
diff --git a/plugins/module_utils/network/vyos/facts/logging_global/logging_global.py b/plugins/module_utils/network/vyos/facts/logging_global/logging_global.py
index 8b60bef9..da80d967 100644
--- a/plugins/module_utils/network/vyos/facts/logging_global/logging_global.py
+++ b/plugins/module_utils/network/vyos/facts/logging_global/logging_global.py
@@ -1,101 +1,100 @@
# -*- coding: utf-8 -*-
# Copyright 2021 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
"""
The vyos logging_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 ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.logging_global.logging_global import (
Logging_globalArgs,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.logging_global import (
Logging_globalTemplate,
)
class Logging_globalFacts(object):
"""The vyos logging_global facts class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = Logging_globalArgs.argument_spec
def get_logging_data(self, connection):
return connection.get("show configuration commands | grep syslog")
def process_facts(self, objFinal):
if objFinal:
- for ke, vl in iteritems(objFinal):
+ for ke, vl in objFinal.items():
if ke == "files":
_files = []
for k, v in vl.items():
_files.append(v)
objFinal[ke] = _files
objFinal[ke] = sorted(objFinal[ke], key=lambda item: item["path"])
elif ke == "hosts":
_hosts = []
for k, v in vl.items():
_hosts.append(v)
objFinal[ke] = _hosts
objFinal[ke] = sorted(objFinal[ke], key=lambda item: item["hostname"])
elif ke == "users":
_users = []
for k, v in vl.items():
_users.append(v)
objFinal[ke] = _users
objFinal[ke] = sorted(objFinal[ke], key=lambda item: item["username"])
elif ke == "console" or ke == "global_params":
if objFinal[ke].get("facilities"):
objFinal[ke]["facilities"] = sorted(
objFinal[ke]["facilities"],
key=lambda item: item["facility"],
)
return objFinal
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for Logging_global network resource
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
facts = {}
objs = []
if not data:
data = self.get_logging_data(connection)
# parse native config using the Logging_global template
logging_global_parser = Logging_globalTemplate(lines=data.splitlines(), module=self._module)
objs = logging_global_parser.parse()
ansible_facts["ansible_network_resources"].pop("logging_global", None)
objs = self.process_facts(objs)
params = utils.remove_empties(
logging_global_parser.validate_config(
self.argument_spec,
{"config": objs},
redact=True,
),
)
facts["logging_global"] = params.get("config", {})
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
diff --git a/plugins/module_utils/network/vyos/facts/snmp_server/snmp_server.py b/plugins/module_utils/network/vyos/facts/snmp_server/snmp_server.py
index e70a15f9..d961a0ff 100644
--- a/plugins/module_utils/network/vyos/facts/snmp_server/snmp_server.py
+++ b/plugins/module_utils/network/vyos/facts/snmp_server/snmp_server.py
@@ -1,114 +1,113 @@
# -*- coding: utf-8 -*-
# Copyright 2022 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
"""
The vyos snmp_server 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.
"""
import re
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.snmp_server.snmp_server import (
Snmp_serverArgs,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.snmp_server import (
Snmp_serverTemplate,
)
class Snmp_serverFacts(object):
"""The vyos snmp_server facts class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = Snmp_serverArgs.argument_spec
def get_config(self, connection):
return connection.get("show configuration commands | grep snmp")
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for Snmp_server network resource
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
facts = {}
objs = []
config_lines = []
if not data:
data = self.get_config(connection)
for resource in data.splitlines():
config_lines.append(re.sub("'", "", resource))
# parse native config using the Snmp_server template
snmp_server_parser = Snmp_serverTemplate(lines=config_lines, module=self._module)
objs = snmp_server_parser.parse()
if objs:
if "communities" in objs:
for k in objs["communities"].values():
- for param, val in iteritems(k):
+ for param, val in k.items():
if param in ["clients", "networks"]:
if None in val:
val.remove(None)
val.sort()
objs["communities"] = sorted(
list(objs["communities"].values()),
key=lambda k, sk="name": k[sk],
)
if "listen_addresses" in objs:
objs["listen_addresses"] = sorted(
list(objs["listen_addresses"].values()),
key=lambda k, sk="address": k[sk],
)
if "snmp_v3" in objs:
if "groups" in objs["snmp_v3"]:
objs["snmp_v3"]["groups"] = sorted(
list(objs["snmp_v3"]["groups"].values()),
key=lambda k, sk="group": k[sk],
)
if "trap_targets" in objs["snmp_v3"]:
objs["snmp_v3"]["trap_targets"] = sorted(
list(objs["snmp_v3"]["trap_targets"].values()),
key=lambda k, sk="address": k[sk],
)
if "users" in objs["snmp_v3"]:
objs["snmp_v3"]["users"] = sorted(
list(objs["snmp_v3"]["users"].values()),
key=lambda k, sk="user": k[sk],
)
if "views" in objs["snmp_v3"]:
objs["snmp_v3"]["views"] = sorted(
list(objs["snmp_v3"]["views"].values()),
key=lambda k, sk="view": k[sk],
)
else:
objs = {}
ansible_facts["ansible_network_resources"].pop("snmp_server", None)
params = utils.remove_empties(
snmp_server_parser.validate_config(self.argument_spec, {"config": objs}, redact=True),
)
facts["snmp_server"] = params.get("config", {})
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
diff --git a/plugins/module_utils/network/vyos/rm_templates/bgp_global.py b/plugins/module_utils/network/vyos/rm_templates/bgp_global.py
index 621f65ea..8c2e2f55 100644
--- a/plugins/module_utils/network/vyos/rm_templates/bgp_global.py
+++ b/plugins/module_utils/network/vyos/rm_templates/bgp_global.py
@@ -1,1860 +1,1859 @@
# -*- coding: utf-8 -*-
# Copyright 2021 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
"""
The Bgp_global parser templates file. This contains
a list of parser definitions and associated functions that
facilitates both facts gathering and native command generation for
the given network resource.
"""
import re
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
NetworkTemplate,
)
def _tmplt_bgp_params_confederation(config_data):
command = []
for list_el in config_data["bgp_params"]["confederation"]:
- for k, v in iteritems(list_el):
+ for k, v in list_el.items():
command.append(
"protocols bgp {as_number} parameters confederation ".format(**config_data)
+ k
+ " "
+ str(v),
)
return command
def _tmplt_bgp_maximum_paths(config_data):
command = []
for list_el in config_data["maximum_paths"]:
command.append(
"protocols bgp {as_number} maximum-paths ".format(**config_data)
+ list_el["path"]
+ " "
+ str(list_el["count"]),
)
return command
def _tmplt_delete_bgp_maximum_paths(config_data):
command = "protocols bgp {as_number} maximum-paths".format(**config_data)
return command
def _tmplt_bgp_params_default(config_data):
command = "protocols bgp {as_number} parameters default".format(**config_data)
if config_data["bgp_params"]["default"].get("no_ipv4_unicast"):
command += " no-ipv4-unicast"
if config_data["bgp_params"]["default"].get("local_pref"):
command += " local-pref {local_pref}".format(**config_data["bgp_params"]["default"])
return command
def _tmplt_bgp_neighbor_timers(config_data):
command = []
- for k, v in iteritems(config_data["neighbor"]["timers"]):
+ for k, v in config_data["neighbor"]["timers"].items():
command.append(
"protocols bgp {as_number} neighbor ".format(**config_data)
+ config_data["neighbor"]["address"]
+ " timers "
+ k
+ " "
+ str(v),
)
return command
def _tmplt_bgp_timers(config_data):
command = []
- for k, v in iteritems(config_data["timers"]):
+ for k, v in config_data["timers"].items():
command.append(
"protocols bgp {as_number} ".format(**config_data) + "timers " + k + " " + str(v),
)
return command
def _tmplt_bgp_neighbor_attribute_unchanged_as_path(config_data):
command = "protocols bgp {as_number} ".format(
**config_data,
) + "neighbor {address} attribute-unchanged as-path".format(**config_data["neighbor"])
return command
def _tmplt_bgp_neighbor_attribute_unchanged_med(config_data):
command = "protocols bgp {as_number} ".format(
**config_data,
) + "neighbor {address} attribute-unchanged med".format(**config_data["neighbor"])
return command
def _tmplt_bgp_neighbor_attribute_unchanged_next_hop(config_data):
command = "protocols bgp {as_number} ".format(
**config_data,
) + "neighbor {address} attribute-unchanged next-hop".format(**config_data["neighbor"])
return command
def _tmplt_bgp_neighbor_distribute_list(config_data):
command = []
for list_el in config_data["neighbor"]["distribute_list"]:
command.append(
"protocols bgp {as_number} ".format(**config_data)
+ "neighbor {address} distribute-list ".format(**config_data["neighbor"])
+ list_el["action"]
+ " "
+ str(list_el["acl"]),
)
return command
def _tmplt_bgp_neighbor_route_map(config_data):
command = []
for list_el in config_data["neighbor"]["route_map"]:
command.append(
"protocols bgp {as_number} ".format(**config_data)
+ "neighbor {address} route-map ".format(**config_data["neighbor"])
+ list_el["action"]
+ " "
+ str(list_el["route_map"]),
)
return command
def _tmplt_bgp_neighbor_prefix_list(config_data):
command = []
for list_el in config_data["neighbor"]["prefix_list"]:
command.append(
"protocols bgp {as_number} ".format(**config_data)
+ "neighbor {address} prefix-list ".format(**config_data["neighbor"])
+ list_el["action"]
+ " "
+ str(list_el["prefix_list"]),
)
return command
def _tmplt_bgp_neighbor_filter_list(config_data):
command = []
for list_el in config_data["neighbor"]["filter_list"]:
command.append(
"protocols bgp {as_number} ".format(**config_data)
+ "neighbor {address} filter-list ".format(**config_data["neighbor"])
+ list_el["action"]
+ " "
+ str(list_el["path_list"]),
)
return command
def _tmplt_bgp_params_distance(config_data):
command = (
"protocols bgp {as_number} parameters distance global ".format(**config_data)
+ config_data["bgp_params"]["distance"]["type"]
+ " "
+ str(config_data["bgp_params"]["distance"]["value"])
)
return command
class Bgp_globalTemplate(NetworkTemplate):
def __init__(self, lines=None, module=None):
prefix = {"set": "set", "remove": "delete"}
super(Bgp_globalTemplate, self).__init__(
lines=lines,
tmplt=self,
prefix=prefix,
module=module,
)
# fmt: off
PARSERS = [
{
"name": "router",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }}",
"compval": "as_number",
"result": {
"as_number": "{{ as_num }}",
},
},
{
"name": "maximum_paths",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+maximum-paths
\s+(?P<path>ebgp|ibgp)
\s+(?P<count>\d+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_maximum_paths,
"remval": _tmplt_delete_bgp_maximum_paths,
"compval": "maximum_paths",
"result": {
"as_number": "{{ as_num }}",
"maximum_paths": [
{
"path": "{{ path }}",
"count": "{{ count }}",
},
],
},
},
{
"name": "neighbor.advertisement_interval",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+advertisement-interval
\s+(?P<interval>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} advertisement-interval {{ neighbor.advertisement_interval }}",
"remval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} advertisement-interval",
"compval": "neighbor.advertisement_interval",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"advertisement_interval": "{{ interval }}",
},
},
},
},
{
"name": "neighbor.allowas_in",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+allowas-in
\s+number
\s+(?P<num>\'\d+\')
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} allowas-in number {{ neighbor.allowas_in }}",
"compval": "neighbor.allowas_in",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"allowas_in": "{{ count }}",
},
},
},
},
{
"name": "neighbor.as_override",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+as-override
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} as-override",
"compval": "neighbor.as_override",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"as_override": "{{ True }}",
},
},
},
},
{
"name": "neighbor.attribute_unchanged.as_path",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+attribute-unchanged
\s+(?P<val>as-path)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_attribute_unchanged_as_path,
"compval": "neighbor.attribute_unchanged",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"attribute_unchanged": {
"{{ 'as_path' }}": "{{ True }}",
},
},
},
},
},
{
"name": "neighbor.attribute_unchanged.med",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+attribute-unchanged
\s+(?P<val>med)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_attribute_unchanged_med,
"compval": "neighbor.attribute_unchanged",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"attribute_unchanged": {
"{{ 'med' }}": "{{ True }}",
},
},
},
},
},
{
"name": "neighbor.attribute_unchanged.next_hop",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+attribute-unchanged
\s+(?P<val>next-hop)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_attribute_unchanged_next_hop,
"compval": "neighbor.attribute_unchanged",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"attribute_unchanged": {
"{{ 'next_hop' }}": "{{ True }}",
},
},
},
},
},
{
"name": "neighbor.capability_dynamic",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+capability
\s+(?P<dynamic>dynamic)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} capability dynamic",
"compval": "neighbor.capability.dynamic",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"capability": {
"dynamic": "{{ True if dynamic is defined}}",
},
},
},
},
},
{
"name": "neighbor.capability_orf",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+capability
\s+orf
\s+prefix-list
\s+(?P<orf>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} capability orf prefix-list {{ neighbor.capability.orf }}",
"compval": "neighbor.capability.orf",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"capability": {
"orf": "{{ orf }}",
},
},
},
},
},
{
"name": "neighbor.default_originate",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+default-originate
\s+route-map
\s+(?P<map>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} default-originate route-map {{ neighbor.default_originate }}",
"compval": "neighbor.advertisement_interval",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"default_originate": "{{ map }}",
},
},
},
},
{
"name": "neighbor.description",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+description
\s+(?P<desc>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} description {{ neighbor.description }}",
"compval": "neighbor.description",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"description": "{{ desc }}",
},
},
},
},
{
"name": "neighbor.disable_capability_negotiation",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+disable-capability-negotiation
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} disable-capability-negotiation",
"compval": "neighbor.disable_capability_negotiation",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"disable_capability_negotiation": "{{ True }}",
},
},
},
},
{
"name": "neighbor.disable_connected_check",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+disable-connected-check
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} disable-connected-check",
"compval": "neighbor.disable_connected_check",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"disable_connected_check": "{{ True }}",
},
},
},
},
{
"name": "neighbor.disable_send_community",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+disable-send-community
\s+(?P<comm>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} disable-send-community {{ neighbor.disable_send_community }}",
"compval": "neighbor.disable_send_community",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"disable_send_community": "{{ comm }}",
},
},
},
},
{
"name": "neighbor.distribute_list",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+distribute-list
\s+(?P<action>export|import)
\s+(?P<list>\d+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_distribute_list,
"compval": "neighbor.distribute_list",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"distribute_list": [
{
"action": "{{ action }}",
"acl": "{{ list }}",
},
],
},
},
},
},
{
"name": "neighbor.ebgp_multihop",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+ebgp-multihop
\s+(?P<hop>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} ebgp-multihop {{ neighbor.ebgp_multihop }}",
"compval": "neighbor.ebgp_multihop",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"ebgp_multihop": "{{ hop|int }}",
},
},
},
},
{
"name": "neighbor.filter_list",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+filter-list
\s+(?P<action>export|import)
\s+(?P<list>\S+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_filter_list,
"compval": "neighbor.filter_list",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"filter_list": [
{
"action": "{{ action }}",
"path_list": "{{ list }}",
},
],
},
},
},
},
{
"name": "neighbor.local_as",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+local-as
\s+(?P<as>\S+)
\s+no-prepend
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} local-as {{ neighbor.local_as }} no-prepend",
"compval": "neighbor.local_as",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"local_as": "{{ as }}",
},
},
},
},
{
"name": "neighbor.maximum_prefix",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+maximum-prefix
\s+(?P<num>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} maximum-prefix {{ neighbor.maximum_prefix }}",
"compval": "neighbor.maximum_prefix",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"maximum_prefix": "{{ num }}",
},
},
},
},
{
"name": "neighbor.nexthop_self",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+nexthop-self
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} nexthop-self",
"compval": "neighbor.nexthop_self",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"nexthop_self": "{{ True }}",
},
},
},
},
{
"name": "neighbor.override_capability",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+override-capability
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} override-capability",
"compval": "neighbor.override_capability",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"override_capability": "{{ True }}",
},
},
},
},
{
"name": "neighbor.passive",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+passive
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} passive",
"compval": "neighbor.passive",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"passive": "{{ True }}",
},
},
},
},
{
"name": "neighbor.password",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+password
\s+(?P<pwd>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} password {{ neighbor.password }}",
"compval": "neighbor.password",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"password": "{{ pwd }}",
},
},
},
},
{
"name": "neighbor.peer_group_name",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+peer-group
\s+(?P<name>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} peer-group {{ neighbor.peer_group_name }}",
"compval": "neighbor.peer_group_name",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"peer_group_name": "{{ name }}",
},
},
},
},
{
"name": "neighbor.port",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+port
\s+(?P<num>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} port {{ neighbor.port }}",
"compval": "neighbor.port",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"port": "{{ num|int }}",
},
},
},
},
{
"name": "neighbor.prefix_list",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+prefix-list
\s+(?P<action>export|import)
\s+(?P<list>\S+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_prefix_list,
"compval": "neighbor.prefix_list",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"prefix_list": [
{
"action": "{{ action }}",
"prefix_list": "{{ list }}",
},
],
},
},
},
},
{
"name": "neighbor.remote_as",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+remote-as
\s+(?P<num>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} remote-as {{ neighbor.remote_as }}",
"compval": "neighbor.remote_as",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"remote_as": "{{ num|int }}",
},
},
},
},
{
"name": "neighbor.remove_private_as",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+remote-private-as
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} remote-private-as",
"compval": "neighbor.remove_private_as",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"remove_private_as": "{{ True }}",
},
},
},
},
{
"name": "neighbor.route_map",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+route-map
\s+(?P<action>export|import)
\s+(?P<map>\S+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_route_map,
"compval": "neighbor.route_map",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"route_map": [
{
"action": "{{ action }}",
"route_map": "{{ map }}",
},
],
},
},
},
},
{
"name": "neighbor.route_reflector_client",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+route-reflector-client
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} router-reflector-client",
"compval": "neighbor.route_reflector_client",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"route_reflector_client": "{{ True }}",
},
},
},
},
{
"name": "neighbor.route_server_client",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+route-server-client
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} route-server-client",
"compval": "neighbor.route_server_client",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"route_server_client": "{{ True }}",
},
},
},
},
{
"name": "neighbor.shutdown",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+shutdown
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} shutdown",
"compval": "neighbor.shutdown",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"shutdown": "{{ True }}",
},
},
},
},
{
"name": "neighbor.soft_reconfiguration",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+soft-reconfiguration
\s+inbound
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} soft-reconfiguration",
"compval": "neighbor.soft_reconfiguration",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"soft_reconfiguration": "{{ True }}",
},
},
},
},
{
"name": "neighbor.strict_capability_match",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+strict-capability-match
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} strict-capability-match",
"compval": "neighbor.strict_capability_match",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"strict_capability_match": "{{ True }}",
},
},
},
},
{
"name": "neighbor.unsuppress_map",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+unsuppress-map
\s+(?P<map>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} unsuppress-map {{ neighbor.unsuppress_map }}",
"compval": "neighbor.unsuppress_map",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"unsuppress_map": "{{ map }}",
},
},
},
},
{
"name": "neighbor.update_source",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+update-source
\s+(?P<src>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} update-source {{ neighbor.update_source }}",
"compval": "neighbor.update_source",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"update_source": "{{ src }}",
},
},
},
},
{
"name": "neighbor.weight",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+weight
\s+(?P<num>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} weight {{ neighbor.weight }}",
"compval": "neighbor.weight",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"weight": "{{ num }}",
},
},
},
},
{
"name": "neighbor.ttl_security",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+ttl-security
\s+(?P<ttl>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} ttl-security {{ neighbor.ttl_security }}",
"compval": "neighbor.ttl_security",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"ttl_security": "{{ ttl|int }}",
},
},
},
},
{
"name": "neighbor.timers",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+neighbor
\s+(?P<address>\S+)
\s+timers
\s+(?P<type>connect|holdtime|keepalive)
\s+(?P<sec>\S+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_timers,
"remval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} timers",
"compval": "neighbor.timers",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"timers": {
"{{ type }}": "{{ sec }}",
},
},
},
},
},
{
"name": "timers",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+timers
\s+(?P<type>\S+)
\s+(?P<val>\S+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_timers,
"remval": "protocols bgp {{ as_number }} timers",
"compval": "timers",
"result": {
"as_number": "{{ as_num }}",
"timers": {
"{{ type }}": "{{ val }}",
},
},
},
{
"name": "bgp_params.always_compare_med",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+always-compare-med
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters always-compare-med",
"compval": "bgp_params.always_compare_med",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"always_compare_med": "{{ True }}",
},
},
},
{
"name": "bgp_params.bestpath.as_path",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+bestpath
\s+as-path
\s+(?P<path>confed|ignore)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters bestpath as-path {{ bgp_params.bestpath.as_path }}",
"compval": "bgp_params.bestpath.as_path",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"bestpath": {
"as_path": "{{ path }}",
},
},
},
},
{
"name": "bgp_params.bestpath.compare_routerid",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+bestpath
\s+compare-routerid
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters bestpath compare-routerid",
"compval": "bgp_params.bestpath.compare_routerid",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"bestpath": {
"compare_routerid": "{{ True }}",
},
},
},
},
{
"name": "bgp_params.bestpath.med",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+bestpath
\s+med
\s+(?P<path>confed|missing-as-worst)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters bestpath med {{ bestpath.med }}",
"compval": "bgp_params.bestpath.med",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"bestpath": {
"med": "{{ path }}",
},
},
},
},
{
"name": "bgp_params.cluster_id",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+cluster-id
\s+(?P<id>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters cluster-id {{ bgp_params.cluster_id }}",
"compval": "bgp_params.cluster_id",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"cluster_id": "{{ id }}",
},
},
},
{
"name": "bgp_params.confederation",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+confederation
\s+(?P<type>identifier|peers)
\s+(?P<val>\S+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_params_confederation,
"compval": "bgp_params.always_compare_med",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"confederation": [
{
"peers": "{{ val if type == 'peers' }}",
"identifier": "{{ val if type == 'identifier' }}",
},
],
},
},
},
{
"name": "bgp_params.dampening_half_life",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+dampening
\s+half-life
\s+(?P<val>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters dampening half-life {{ bgp_params.dampening.half_life}}",
"compval": "bgp_params.dampening.half_life",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"dampening": {
"half_life": "{{ val }}",
},
},
},
},
{
"name": "bgp_params.dampening_max_suppress_time",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+dampening
\s+max-suppress-time
\s+(?P<val>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters dampening max-suppress-time {{ bgp_params.dampening.max_suppress_time}}",
"compval": "bgp_params.dampening.max_suppress_time",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"dampening": {
"max_suppress_time": "{{ val }}",
},
},
},
},
{
"name": "bgp_params.dampening_re_use",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+dampening
\s+re-use
\s+(?P<val>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters dampening re-use {{ bgp_params.dampening.re_use}}",
"compval": "bgp_params.dampening.re_use",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"dampening": {
"re_use": "{{ val }}",
},
},
},
},
{
"name": "bgp_params.dampening_start_suppress_time",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+dampening
\s+start-suppress-time
\s+(?P<val>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters dampening start-suppress-time {{ bgp_params.dampening.start_suppress_time}}",
"compval": "bgp_params.dampening.start_suppress_time",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"dampening": {
"start_suppress_time": "{{ val }}",
},
},
},
},
{
"name": "bgp_params.default",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+default
\s*(?P<no_ipv4_unicast>no-ipv4-unicast)*
\s*(?P<local_pref>local-pref\s\S+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_params_default,
"remval": "protocols bgp {{ as_number }} parameters default",
"compval": "bgp_params.default",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"default": {
"no_ipv4_unicast": "{{ True if no_ipv4_unicast is defined }}",
"local_pref": "{{ local_pref.split(" " )[1] if local_pref is defined }}",
},
},
},
},
{
"name": "bgp_params.deterministic_med",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+deterministic-med
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters deterministic-med",
"compval": "bgp_params.deterministic_med",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"deterministic_med": "{{ True }}",
},
},
},
{
"name": "bgp_params.disbale_network_import_check",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+disable-network-import-check
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters disable-network-import-check",
"compval": "bgp_params.disable_network_import_check",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"disable_network_import_check": "{{ True }}",
},
},
},
{
"name": "bgp_params.distance.prefix",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+distance\sprefix
\s+(?P<prefix>\S+)
\s+distance
\s+(?P<val>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters distance prefix {{ bgp_params.distance.prefix }} distance {{ bgp_params.distance.value }}",
"compval": "bgp_params.distance.prefix",
"remval": "protocols bgp {{ as_number }} parameters distance prefix",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"distance": [
{
"prefix": "{{ prefix }}",
"value": "{{ val }}",
},
],
},
},
},
{
"name": "bgp_params.distance.global",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+distance\sglobal
\s+(?P<type>\S+)
\s+(?P<val>\S+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_params_distance,
"remval": "protocols bgp {{ as_number }} parameters distance global",
"compval": "bgp_params.distance",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"distance": [
{
"type": "{{ type }}",
"value": "{{ val }}",
},
],
},
},
},
{
"name": "bgp_params.enforce_first_as",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+enforce-first-as
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters enforce-first-as",
"compval": "bgp_params.enforce_first_as",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"enforce_first_as": "{{ True }}",
},
},
},
{
"name": "bgp_params.graceful_restart",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+graceful-restart\s+stalepath-time
\s+(?P<val>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters graceful-restart stalepath-time {{ bgp_params.graceful_restart }}",
"compval": "bgp_params.graceful_restart",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"graceful_restart": "{{ val }}",
},
},
},
{
"name": "bgp_params.log_neighbor_changes",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+log-neighbor-changes
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters log-neighbor-changes",
"compval": "bgp_params.log_neighbor_changes",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"log_neighbor_changes": "{{ True }}",
},
},
},
{
"name": "bgp_params.no_client_to_client_reflection",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+no-client-to-client-reflection
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters no-client-to-client-reflection",
"compval": "bgp_params.log_neighbor_changes",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"no_client_to_client_reflection": "{{ True }}",
},
},
},
{
"name": "bgp_params.no_fast_external_failover",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+no-fast-external-failover
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters no-fast-external-failover",
"compval": "bgp_params.no_fast_external_failover",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"no_fast_external_failover": "{{ True }}",
},
},
},
{
"name": "bgp_params.routerid",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+router-id
\s+(?P<id>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters router-id {{ bgp_params.router_id }}",
"compval": "bgp_params.router_id",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"router_id": "{{ id }}",
},
},
},
{
"name": "bgp_params.scan_time",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P<as_num>\d+)
\s+parameters
\s+scan-time
\s+(?P<sec>\d+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters scan-time {{ bgp_params.scan_time }}",
"compval": "bgp_params.scan_time",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"scan_time": "{{ val }}",
},
},
},
]
# fmt: on
diff --git a/plugins/module_utils/network/vyos/rm_templates/bgp_global_14.py b/plugins/module_utils/network/vyos/rm_templates/bgp_global_14.py
index b8beb923..39855c1c 100644
--- a/plugins/module_utils/network/vyos/rm_templates/bgp_global_14.py
+++ b/plugins/module_utils/network/vyos/rm_templates/bgp_global_14.py
@@ -1,1799 +1,1795 @@
# -*- coding: utf-8 -*-
# Copyright 2021 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
"""
The Bgp_global parser templates file. This contains
a list of parser definitions and associated functions that
facilitates both facts gathering and native command generation for
the given network resource.
"""
import re
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
NetworkTemplate,
)
def _tmplt_bgp_params_confederation(config_data):
command = []
for list_el in config_data["bgp_params"]["confederation"]:
- for k, v in iteritems(list_el):
+ for k, v in list_el.items():
command.append(
- "protocols bgp parameters confederation ".format(**config_data)
- + k
- + " "
- + str(v),
+ "protocols bgp parameters confederation ".format(**config_data) + k + " " + str(v),
)
return command
def _tmplt_bgp_maximum_paths(config_data):
command = []
for list_el in config_data["maximum_paths"]:
command.append(
"protocols bgp maximum-paths ".format(**config_data)
+ list_el["path"]
+ " "
+ str(list_el["count"]),
)
return command
def _tmplt_delete_bgp_maximum_paths(config_data):
command = "protocols bgp maximum-paths".format(**config_data)
return command
def _tmplt_bgp_params_default(config_data):
command = "protocols bgp parameters default".format(**config_data)
if config_data["bgp_params"]["default"].get("no_ipv4_unicast"):
command += " no-ipv4-unicast"
if config_data["bgp_params"]["default"].get("local_pref"):
command += " local-pref {local_pref}".format(**config_data["bgp_params"]["default"])
return command
def _tmplt_bgp_neighbor_timers(config_data):
command = []
- for k, v in iteritems(config_data["neighbor"]["timers"]):
+ for k, v in config_data["neighbor"]["timers"].items():
command.append(
"protocols bgp neighbor ".format(**config_data)
+ config_data["neighbor"]["address"]
+ " timers "
+ k
+ " "
+ str(v),
)
return command
def _tmplt_bgp_timers(config_data):
command = []
- for k, v in iteritems(config_data["timers"]):
+ for k, v in config_data["timers"].items():
command.append(
"protocols bgp ".format(**config_data) + "timers " + k + " " + str(v),
)
return command
def _tmplt_bgp_neighbor_attribute_unchanged_as_path(config_data):
command = "protocols bgp ".format(
**config_data,
) + "neighbor {address} attribute-unchanged as-path".format(**config_data["neighbor"])
return command
def _tmplt_bgp_neighbor_attribute_unchanged_med(config_data):
command = "protocols bgp ".format(
**config_data,
) + "neighbor {address} attribute-unchanged med".format(**config_data["neighbor"])
return command
def _tmplt_bgp_neighbor_attribute_unchanged_next_hop(config_data):
command = "protocols bgp ".format(
**config_data,
) + "neighbor {address} attribute-unchanged next-hop".format(**config_data["neighbor"])
return command
def _tmplt_bgp_neighbor_distribute_list(config_data):
command = []
for list_el in config_data["neighbor"]["distribute_list"]:
command.append(
"protocols bgp ".format(**config_data)
+ "neighbor {address} distribute-list ".format(**config_data["neighbor"])
+ list_el["action"]
+ " "
+ str(list_el["acl"]),
)
return command
def _tmplt_bgp_neighbor_route_map(config_data):
command = []
for list_el in config_data["neighbor"]["route_map"]:
command.append(
"protocols bgp ".format(**config_data)
+ "neighbor {address} route-map ".format(**config_data["neighbor"])
+ list_el["action"]
+ " "
+ str(list_el["route_map"]),
)
return command
def _tmplt_bgp_neighbor_prefix_list(config_data):
command = []
for list_el in config_data["neighbor"]["prefix_list"]:
command.append(
"protocols bgp ".format(**config_data)
+ "neighbor {address} prefix-list ".format(**config_data["neighbor"])
+ list_el["action"]
+ " "
+ str(list_el["prefix_list"]),
)
return command
def _tmplt_bgp_neighbor_filter_list(config_data):
command = []
for list_el in config_data["neighbor"]["filter_list"]:
command.append(
"protocols bgp ".format(**config_data)
+ "neighbor {address} filter-list ".format(**config_data["neighbor"])
+ list_el["action"]
+ " "
+ str(list_el["path_list"]),
)
return command
def _tmplt_bgp_params_distance(config_data):
command = (
"protocols bgp parameters distance global ".format(**config_data)
+ config_data["bgp_params"]["distance"]["type"]
+ " "
+ str(config_data["bgp_params"]["distance"]["value"])
)
return command
class Bgp_globalTemplate14(NetworkTemplate):
def __init__(self, lines=None, module=None):
prefix = {"set": "set", "remove": "delete"}
super(Bgp_globalTemplate14, self).__init__(
lines=lines,
tmplt=self,
prefix=prefix,
module=module,
)
# fmt: off
PARSERS = [
{
"name": "router",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s*system-as
\s+(?P<as_num>\d+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp system-as {{ as_number }}",
"remval": "protocols bgp",
"compval": "as_number",
"result": {
"as_number": "{{ as_num }}",
},
},
{
"name": "maximum_paths",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+maximum-paths
\s+(?P<path>ebgp|ibgp)
\s+(?P<count>\d+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_maximum_paths,
"remval": _tmplt_delete_bgp_maximum_paths,
"compval": "maximum_paths",
"result": {
"as_number": "{{ as_num }}",
"maximum_paths": [
{
"path": "{{ path }}",
"count": "{{ count }}",
},
],
},
},
{
"name": "neighbor.advertisement_interval",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+advertisement-interval
\s+(?P<interval>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} advertisement-interval {{ neighbor.advertisement_interval }}",
"remval": "protocols bgp neighbor {{ neighbor.address }} advertisement-interval",
"compval": "neighbor.advertisement_interval",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"advertisement_interval": "{{ interval }}",
},
},
},
},
{
"name": "neighbor.allowas_in",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+allowas-in
\s+number
\s+(?P<num>\'\d+\')
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} allowas-in number {{ neighbor.allowas_in }}",
"compval": "neighbor.allowas_in",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"allowas_in": "{{ count }}",
},
},
},
},
{
"name": "neighbor.as_override",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+as-override
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} as-override",
"compval": "neighbor.as_override",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"as_override": "{{ True }}",
},
},
},
},
{
"name": "neighbor.attribute_unchanged.as_path",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+attribute-unchanged
\s+(?P<val>as-path)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_attribute_unchanged_as_path,
"compval": "neighbor.attribute_unchanged",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"attribute_unchanged": {
"{{ 'as_path' }}": "{{ True }}",
},
},
},
},
},
{
"name": "neighbor.attribute_unchanged.med",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+attribute-unchanged
\s+(?P<val>med)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_attribute_unchanged_med,
"compval": "neighbor.attribute_unchanged",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"attribute_unchanged": {
"{{ 'med' }}": "{{ True }}",
},
},
},
},
},
{
"name": "neighbor.attribute_unchanged.next_hop",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+attribute-unchanged
\s+(?P<val>next-hop)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_attribute_unchanged_next_hop,
"compval": "neighbor.attribute_unchanged",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"attribute_unchanged": {
"{{ 'next_hop' }}": "{{ True }}",
},
},
},
},
},
{
"name": "neighbor.capability_dynamic",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+capability
\s+(?P<dynamic>dynamic)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} capability dynamic",
"compval": "neighbor.capability.dynamic",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"capability": {
"dynamic": "{{ True if dynamic is defined}}",
},
},
},
},
},
{
"name": "neighbor.capability_orf",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+capability
\s+orf
\s+prefix-list
\s+(?P<orf>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} capability orf prefix-list {{ neighbor.capability.orf }}",
"compval": "neighbor.capability.orf",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"capability": {
"orf": "{{ orf }}",
},
},
},
},
},
{
"name": "neighbor.default_originate",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+default-originate
\s+route-map
\s+(?P<map>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} default-originate route-map {{ neighbor.default_originate }}",
"compval": "neighbor.advertisement_interval",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"default_originate": "{{ map }}",
},
},
},
},
{
"name": "neighbor.description",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+description
\s+(?P<desc>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} description {{ neighbor.description }}",
"compval": "neighbor.description",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"description": "{{ desc }}",
},
},
},
},
{
"name": "neighbor.disable_capability_negotiation",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+disable-capability-negotiation
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} disable-capability-negotiation",
"compval": "neighbor.disable_capability_negotiation",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"disable_capability_negotiation": "{{ True }}",
},
},
},
},
{
"name": "neighbor.disable_connected_check",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+disable-connected-check
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} disable-connected-check",
"compval": "neighbor.disable_connected_check",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"disable_connected_check": "{{ True }}",
},
},
},
},
{
"name": "neighbor.disable_send_community",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+disable-send-community
\s+(?P<comm>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} disable-send-community {{ neighbor.disable_send_community }}",
"compval": "neighbor.disable_send_community",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"disable_send_community": "{{ comm }}",
},
},
},
},
{
"name": "neighbor.distribute_list",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+distribute-list
\s+(?P<action>export|import)
\s+(?P<list>\d+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_distribute_list,
"compval": "neighbor.distribute_list",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"distribute_list": [
{
"action": "{{ action }}",
"acl": "{{ list }}",
},
],
},
},
},
},
{
"name": "neighbor.ebgp_multihop",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+ebgp-multihop
\s+(?P<hop>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} ebgp-multihop {{ neighbor.ebgp_multihop }}",
"compval": "neighbor.ebgp_multihop",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"ebgp_multihop": "{{ hop|int }}",
},
},
},
},
{
"name": "neighbor.filter_list",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+filter-list
\s+(?P<action>export|import)
\s+(?P<list>\S+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_filter_list,
"compval": "neighbor.filter_list",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"filter_list": [
{
"action": "{{ action }}",
"path_list": "{{ list }}",
},
],
},
},
},
},
{
"name": "neighbor.local_as",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+local-as
\s+(?P<as>\S+)
\s+no-prepend
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} local-as {{ neighbor.local_as }} no-prepend",
"compval": "neighbor.local_as",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"local_as": "{{ as }}",
},
},
},
},
{
"name": "neighbor.maximum_prefix",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+maximum-prefix
\s+(?P<num>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} maximum-prefix {{ neighbor.maximum_prefix }}",
"compval": "neighbor.maximum_prefix",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"maximum_prefix": "{{ num }}",
},
},
},
},
{
"name": "neighbor.nexthop_self",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+nexthop-self
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} nexthop-self",
"compval": "neighbor.nexthop_self",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"nexthop_self": "{{ True }}",
},
},
},
},
{
"name": "neighbor.override_capability",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+override-capability
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} override-capability",
"compval": "neighbor.override_capability",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"override_capability": "{{ True }}",
},
},
},
},
{
"name": "neighbor.passive",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+passive
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} passive",
"compval": "neighbor.passive",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"passive": "{{ True }}",
},
},
},
},
{
"name": "neighbor.password",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+password
\s+(?P<pwd>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} password {{ neighbor.password }}",
"compval": "neighbor.password",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"password": "{{ pwd }}",
},
},
},
},
{
"name": "neighbor.peer_group_name",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+peer-group
\s+(?P<name>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} peer-group {{ neighbor.peer_group_name }}",
"compval": "neighbor.peer_group_name",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"peer_group_name": "{{ name }}",
},
},
},
},
{
"name": "neighbor.port",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+port
\s+(?P<num>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} port {{ neighbor.port }}",
"compval": "neighbor.port",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"port": "{{ num|int }}",
},
},
},
},
{
"name": "neighbor.prefix_list",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+prefix-list
\s+(?P<action>export|import)
\s+(?P<list>\S+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_prefix_list,
"compval": "neighbor.prefix_list",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"prefix_list": [
{
"action": "{{ action }}",
"prefix_list": "{{ list }}",
},
],
},
},
},
},
{
"name": "neighbor.remote_as",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+remote-as
\s+(?P<num>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} remote-as {{ neighbor.remote_as }}",
"compval": "neighbor.remote_as",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"remote_as": "{{ num|int }}",
},
},
},
},
{
"name": "neighbor.remove_private_as",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+remote-private-as
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} remote-private-as",
"compval": "neighbor.remove_private_as",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"remove_private_as": "{{ True }}",
},
},
},
},
{
"name": "neighbor.route_map",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+route-map
\s+(?P<action>export|import)
\s+(?P<map>\S+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_route_map,
"compval": "neighbor.route_map",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"route_map": [
{
"action": "{{ action }}",
"route_map": "{{ map }}",
},
],
},
},
},
},
{
"name": "neighbor.route_reflector_client",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+route-reflector-client
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} router-reflector-client",
"compval": "neighbor.route_reflector_client",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"route_reflector_client": "{{ True }}",
},
},
},
},
{
"name": "neighbor.route_server_client",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+route-server-client
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} route-server-client",
"compval": "neighbor.route_server_client",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"route_server_client": "{{ True }}",
},
},
},
},
{
"name": "neighbor.shutdown",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+shutdown
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} shutdown",
"compval": "neighbor.shutdown",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"shutdown": "{{ True }}",
},
},
},
},
{
"name": "neighbor.soft_reconfiguration",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+soft-reconfiguration
\s+inbound
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} soft-reconfiguration",
"compval": "neighbor.soft_reconfiguration",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"soft_reconfiguration": "{{ True }}",
},
},
},
},
{
"name": "neighbor.strict_capability_match",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+strict-capability-match
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} strict-capability-match",
"compval": "neighbor.strict_capability_match",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"strict_capability_match": "{{ True }}",
},
},
},
},
{
"name": "neighbor.unsuppress_map",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+unsuppress-map
\s+(?P<map>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} unsuppress-map {{ neighbor.unsuppress_map }}",
"compval": "neighbor.unsuppress_map",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"unsuppress_map": "{{ map }}",
},
},
},
},
{
"name": "neighbor.update_source",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+update-source
\s+(?P<src>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} update-source {{ neighbor.update_source }}",
"compval": "neighbor.update_source",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"update_source": "{{ src }}",
},
},
},
},
{
"name": "neighbor.weight",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+weight
\s+(?P<num>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} weight {{ neighbor.weight }}",
"compval": "neighbor.weight",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"weight": "{{ num }}",
},
},
},
},
{
"name": "neighbor.ttl_security",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+ttl-security
\s+(?P<ttl>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} ttl-security {{ neighbor.ttl_security }}",
"compval": "neighbor.ttl_security",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"ttl_security": "{{ ttl|int }}",
},
},
},
},
{
"name": "neighbor.timers",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+neighbor
\s+(?P<address>\S+)
\s+timers
\s+(?P<type>connect|holdtime|keepalive)
\s+(?P<sec>\S+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_timers,
"remval": "protocols bgp neighbor {{ neighbor.address }} timers",
"compval": "neighbor.timers",
"result": {
"as_number": "{{ as_num }}",
"neighbor": {
"{{ address }}": {
"address": "{{ address }}",
"timers": {
"{{ type }}": "{{ sec }}",
},
},
},
},
},
{
"name": "timers",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+timers
\s+(?P<type>\S+)
\s+(?P<val>\S+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_timers,
"remval": "protocols bgp timers",
"compval": "timers",
"result": {
"as_number": "{{ as_num }}",
"timers": {
"{{ type }}": "{{ val }}",
},
},
},
{
"name": "bgp_params.always_compare_med",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+always-compare-med
*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters always-compare-med",
"compval": "bgp_params.always_compare_med",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"always_compare_med": "{{ True }}",
},
},
},
{
"name": "bgp_params.bestpath.as_path",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+bestpath
\s+as-path
\s+(?P<path>confed|ignore)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters bestpath as-path {{ bgp_params.bestpath.as_path }}",
"compval": "bgp_params.bestpath.as_path",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"bestpath": {
"as_path": "{{ path }}",
},
},
},
},
{
"name": "bgp_params.bestpath.compare_routerid",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+bestpath
\s+compare-routerid
*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters bestpath compare-routerid",
"compval": "bgp_params.bestpath.compare_routerid",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"bestpath": {
"compare_routerid": "{{ True }}",
},
},
},
},
{
"name": "bgp_params.bestpath.med",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+bestpath
\s+med
\s+(?P<path>confed|missing-as-worst)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters bestpath med {{ bestpath.med }}",
"compval": "bgp_params.bestpath.med",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"bestpath": {
"med": "{{ path }}",
},
},
},
},
{
"name": "bgp_params.cluster_id",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+cluster-id
\s+(?P<id>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters cluster-id {{ bgp_params.cluster_id }}",
"compval": "bgp_params.cluster_id",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"cluster_id": "{{ id }}",
},
},
},
{
"name": "bgp_params.confederation",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+confederation
\s+(?P<type>identifier|peers)
\s+(?P<val>\S+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_params_confederation,
"compval": "bgp_params.always_compare_med",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"confederation": [
{
"peers": "{{ val if type == 'peers' }}",
"identifier": "{{ val if type == 'identifier' }}",
},
],
},
},
},
{
"name": "bgp_params.dampening_half_life",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+dampening
\s+half-life
\s+(?P<val>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters dampening half-life {{ bgp_params.dampening.half_life}}",
"compval": "bgp_params.dampening.half_life",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"dampening": {
"half_life": "{{ val }}",
},
},
},
},
{
"name": "bgp_params.dampening_max_suppress_time",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+dampening
\s+max-suppress-time
\s+(?P<val>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters dampening max-suppress-time {{ bgp_params.dampening.max_suppress_time}}",
"compval": "bgp_params.dampening.max_suppress_time",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"dampening": {
"max_suppress_time": "{{ val }}",
},
},
},
},
{
"name": "bgp_params.dampening_re_use",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+dampening
\s+re-use
\s+(?P<val>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters dampening re-use {{ bgp_params.dampening.re_use}}",
"compval": "bgp_params.dampening.re_use",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"dampening": {
"re_use": "{{ val }}",
},
},
},
},
{
"name": "bgp_params.dampening_start_suppress_time",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+dampening
\s+start-suppress-time
\s+(?P<val>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters dampening start-suppress-time {{ bgp_params.dampening.start_suppress_time}}",
"compval": "bgp_params.dampening.start_suppress_time",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"dampening": {
"start_suppress_time": "{{ val }}",
},
},
},
},
{
"name": "bgp_params.default",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+default
\s*(?P<no_ipv4_unicast>no-ipv4-unicast)*
\s*(?P<local_pref>local-pref\s\S+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_params_default,
"remval": "protocols bgp parameters default",
"compval": "bgp_params.default",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"default": {
"no_ipv4_unicast": "{{ True if no_ipv4_unicast is defined }}",
"local_pref": "{{ local_pref.split(" " )[1] if local_pref is defined }}",
},
},
},
},
{
"name": "bgp_params.deterministic_med",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+deterministic-med
*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters deterministic-med",
"compval": "bgp_params.deterministic_med",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"deterministic_med": "{{ True }}",
},
},
},
{
"name": "bgp_params.disbale_network_import_check",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+disable-network-import-check
*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters disable-network-import-check",
"compval": "bgp_params.disable_network_import_check",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"disable_network_import_check": "{{ True }}",
},
},
},
{
"name": "bgp_params.distance.prefix",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+distance\sprefix
\s+(?P<prefix>\S+)
\s+distance
\s+(?P<val>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters distance prefix {{ bgp_params.distance.prefix }} distance {{ bgp_params.distance.value }}",
"compval": "bgp_params.distance.prefix",
"remval": "protocols bgp parameters distance prefix",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"distance": [
{
"prefix": "{{ prefix }}",
"value": "{{ val }}",
},
],
},
},
},
{
"name": "bgp_params.distance.global",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+distance\sglobal
\s+(?P<type>\S+)
\s+(?P<val>\S+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_params_distance,
"remval": "protocols bgp parameters distance global",
"compval": "bgp_params.distance",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"distance": [
{
"type": "{{ type }}",
"value": "{{ val }}",
},
],
},
},
},
{
"name": "bgp_params.enforce_first_as",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+enforce-first-as
*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters enforce-first-as",
"compval": "bgp_params.enforce_first_as",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"enforce_first_as": "{{ True }}",
},
},
},
{
"name": "bgp_params.graceful_restart",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+graceful-restart\s+stalepath-time
\s+(?P<val>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters graceful-restart stalepath-time {{ bgp_params.graceful_restart }}",
"compval": "bgp_params.graceful_restart",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"graceful_restart": "{{ val }}",
},
},
},
{
"name": "bgp_params.log_neighbor_changes",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+log-neighbor-changes
*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters log-neighbor-changes",
"compval": "bgp_params.log_neighbor_changes",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"log_neighbor_changes": "{{ True }}",
},
},
},
{
"name": "bgp_params.no_client_to_client_reflection",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+no-client-to-client-reflection
*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters no-client-to-client-reflection",
"compval": "bgp_params.log_neighbor_changes",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"no_client_to_client_reflection": "{{ True }}",
},
},
},
{
"name": "bgp_params.no_fast_external_failover",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+no-fast-external-failover
*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters no-fast-external-failover",
"compval": "bgp_params.no_fast_external_failover",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"no_fast_external_failover": "{{ True }}",
},
},
},
{
"name": "bgp_params.routerid",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+router-id
\s+(?P<id>\S+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters router-id {{ bgp_params.router_id }}",
"compval": "bgp_params.router_id",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"router_id": "{{ id }}",
},
},
},
{
"name": "bgp_params.scan_time",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+parameters
\s+scan-time
\s+(?P<sec>\d+)
*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters scan-time {{ bgp_params.scan_time }}",
"compval": "bgp_params.scan_time",
"result": {
"as_number": "{{ as_num }}",
"bgp_params": {
"scan_time": "{{ val }}",
},
},
},
]
# fmt: on
diff --git a/plugins/module_utils/network/vyos/utils/utils.py b/plugins/module_utils/network/vyos/utils/utils.py
index 4c371962..6957f6cb 100644
--- a/plugins/module_utils/network/vyos/utils/utils.py
+++ b/plugins/module_utils/network/vyos/utils/utils.py
@@ -1,288 +1,287 @@
# -*- 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.basic import missing_required_lib
-from ansible.module_utils.six import iteritems
try:
import ipaddress
HAS_IPADDRESS = True
except ImportError:
HAS_IPADDRESS = False
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"""
if interface.startswith("eth"):
return "ethernet"
elif interface.startswith("bond"):
return "bonding"
elif interface.startswith("vti"):
return "vti"
elif interface.startswith("lo"):
return "loopback"
elif interface.startswith("vtun"):
return "openvpn"
elif interface.startswith("wg"):
return "wireguard"
elif interface.startswith("tun"):
return "tunnel"
elif interface.startswith("br"):
return "bridge"
elif interface.startswith("dum"):
return "dummy"
def get_interface_with_vif(interface):
"""Gets virtual interface if any or return as is"""
vlan = None
interface_real = interface
if "." in interface:
interface_real, vlan = interface.split(".")
if vlan is not None:
interface_real = interface_real + " vif " + vlan
return interface_real
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):
+ for key, value in want_dict.items():
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:
"""
if not HAS_IPADDRESS:
raise Exception(missing_required_lib("ipaddress"))
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.
+ This functi 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 in_target_not_none(h, key):
"""
This function checks whether the target exist,key present in target config, and the value is not None.
:param h: target config.
:param key: attribute name.
:return: True/False.
"""
return True if h and key in h and h[key] is not None else False
diff --git a/plugins/modules/vyos_user.py b/plugins/modules/vyos_user.py
index 4a68e559..7aaa45ae 100644
--- a/plugins/modules/vyos_user.py
+++ b/plugins/modules/vyos_user.py
@@ -1,514 +1,513 @@
#!/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 <http://www.gnu.org/licenses/>.
#
DOCUMENTATION = """
module: vyos_user
author: Trishna Guha (@trishnaguha)
short_description: Manage the collection of local users on VyOS device
description:
- This module provides declarative management of the local usernames configured on
network devices. It allows playbooks to manage either individual usernames or the
collection of usernames in the current running config. It also supports purging
usernames from the configuration that are not explicitly defined.
version_added: 1.0.0
extends_documentation_fragment:
- vyos.vyos.vyos
notes:
- Tested against VyOS 1.3.8, 1.4.2, the upcoming 1.5, and the rolling release of spring 2025.
- This module works with connection C(ansible.netcommon.network_cli). See L(the VyOS OS Platform Options,../network/user_guide/platform_vyos.html).
options:
aggregate:
description:
- The set of username objects to be configured on the remote VyOS device. The
list entries can either be the username or a hash of username and properties.
This argument is mutually exclusive with the C(name) argument.
aliases:
- users
- collection
type: list
elements: dict
suboptions:
name:
description:
- The username to be configured on the VyOS device. This argument accepts a string
value and is mutually exclusive with the C(aggregate) argument.
required: True
type: str
full_name:
description:
- The C(full_name) argument provides the full name of the user account to be created
on the remote device. This argument accepts any text string value.
type: str
encrypted_password:
description:
- The encrypted password of the user account on the remote device. Note that unlike
the C(configured_password) argument, this argument ignores the C(update_password)
and updates if the value is different from the one in the device running config.
type: str
configured_password:
description:
- The password to be configured on the VyOS device. The password needs to be provided
in clear and it will be encrypted on the device.
type: str
update_password:
description:
- Since passwords are encrypted in the device running config, this argument will
instruct the module when to change the password. When set to C(always), the
password will always be updated in the device and when set to C(on_create) the
password will be updated only if the username is created.
type: str
choices:
- on_create
- always
state:
description:
- Configures the state of the username definition as it relates to the device
operational configuration. When set to I(present), the username(s) should be
configured in the device active configuration and when set to I(absent) the
username(s) should not be in the device active configuration
type: str
choices:
- present
- absent
public_keys: &public_keys
description:
- Public keys for authentiction over SSH.
type: list
elements: dict
suboptions:
name:
description: Name of the key (usually in the form of user@hostname)
required: true
type: str
key:
description: Public key string (base64 encoded)
required: true
type: str
type:
description: Type of the key
required: true
type: str
choices:
- ssh-dss
- ssh-rsa
- ecdsa-sha2-nistp256
- ecdsa-sha2-nistp384
- ssh-ed25519
- ecdsa-sha2-nistp521
name:
description:
- The username to be configured on the VyOS device. This argument accepts a string
value and is mutually exclusive with the C(aggregate) argument.
type: str
full_name:
description:
- The C(full_name) argument provides the full name of the user account to be created
on the remote device. This argument accepts any text string value.
type: str
encrypted_password:
description:
- The encrypted password of the user account on the remote device. Note that unlike
the C(configured_password) argument, this argument ignores the C(update_password)
and updates if the value is different from the one in the device running config.
type: str
configured_password:
description:
- The password to be configured on the VyOS device. The password needs to be provided
in clear and it will be encrypted on the device.
type: str
update_password:
description:
- Since passwords are encrypted in the device running config, this argument will
instruct the module when to change the password. When set to C(always), the
password will always be updated in the device and when set to C(on_create) the
password will be updated only if the username is created.
default: always
type: str
choices:
- on_create
- always
public_keys: *public_keys
purge:
description:
- Instructs the module to consider the resource definition absolute. It will remove
any previously configured usernames on the device with the exception of the
`admin` user (the current defined set of users).
type: bool
default: false
state:
description:
- Configures the state of the username definition as it relates to the device
operational configuration. When set to I(present), the username(s) should be
configured in the device active configuration and when set to I(absent) the
username(s) should not be in the device active configuration
type: str
default: present
choices:
- present
- absent
"""
EXAMPLES = """
- name: create a new user
vyos.vyos.vyos_user:
name: ansible
configured_password: password
state: present
- name: remove all users except admin
vyos.vyos.vyos_user:
purge: true
- name: set multiple users to level operator
vyos.vyos.vyos_user:
aggregate:
- name: netop
- name: netend
state: present
- name: Change Password for User netop
vyos.vyos.vyos_user:
name: netop
configured_password: '{{ new_password }}'
update_password: always
state: present
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always
type: list
sample:
- set system login user authentication plaintext-password password
"""
import re
from copy import deepcopy
from functools import partial
from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.six import iteritems
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,
)
def spec_to_commands(updates, module):
commands = list()
update_password = module.params["update_password"]
def needs_update(want, have, x):
return want.get(x) and (want.get(x) != have.get(x))
def add(command, want, x):
command.append("set system login user %s %s" % (want["name"], x))
for update in updates:
want, have = update
if want["state"] == "absent":
commands.append("delete system login user %s" % want["name"])
continue
if needs_update(want, have, "full_name"):
add(commands, want, "full-name '%s'" % want["full_name"])
# look both ways for public_keys to handle replacement
want_keys = want.get("public_keys") or dict()
have_keys = have.get("public_keys") or dict()
for key_name in want_keys:
key = want_keys[key_name]
if key_name not in have_keys or key != have_keys[key_name]:
add(
commands,
want,
"authentication public-keys %s key '%s'" % (key["name"], key["key"]),
)
add(
commands,
want,
"authentication public-keys %s type '%s'" % (key["name"], key["type"]),
)
for key_name in have_keys:
if key_name not in want_keys:
commands.append(
"delete system login user %s authentication public-keys %s"
% (want["name"], key_name),
)
if needs_update(want, have, "encrypted_password"):
add(
commands,
want,
"authentication encrypted-password '%s'" % want["encrypted_password"],
)
if needs_update(want, have, "configured_password"):
if update_password == "always" or not have:
add(
commands,
want,
"authentication plaintext-password %s" % want["configured_password"],
)
return commands
def parse_full_name(data):
match = re.search(r"full-name '(\S+)'", data, re.M)
if match:
full_name = match.group(1)[1:-1]
return full_name
def parse_key(data):
match = re.search(r"key '(\S+)'", data, re.M)
if match:
key = match.group(1)
return key
def parse_key_type(data):
match = re.search(r"type '(\S+)'", data, re.M)
if match:
key_type = match.group(1)
return key_type
def parse_public_keys(data):
"""
Parse public keys from the configuration
returning dictionary of dictionaries indexed by key name
"""
match = re.findall(r"public-keys (\S+)", data, re.M)
if not match:
return dict()
keys = dict()
for key in set(match):
regex = r" %s .+$" % key
cfg = re.findall(regex, data, re.M)
cfg = "\n".join(cfg)
obj = {
"name": key,
"key": parse_key(cfg),
"type": parse_key_type(cfg),
}
keys[key] = obj
return keys
def parse_encrypted_password(data):
match = re.search(r"authentication encrypted-password '(\S+)'", data, re.M)
if match:
encrypted_password = match.group(1)
return encrypted_password
def config_to_dict(module):
data = get_config(module)
match = re.findall(r"^set system login user (\S+)", data, re.M)
if not match:
return list()
instances = list()
for user in set(match):
regex = r" %s .+$" % user
cfg = re.findall(regex, data, re.M)
cfg = "\n".join(cfg)
obj = {
"name": user,
"state": "present",
"configured_password": None,
"full_name": parse_full_name(cfg),
"encrypted_password": parse_encrypted_password(cfg),
"public_keys": parse_public_keys(cfg),
}
instances.append(obj)
return instances
def get_param_value(key, item, module):
# if key doesn't exist in the item, get it from module.params
if not item.get(key):
value = module.params[key]
# validate the param value (if validator func exists)
validator = globals().get("validate_%s" % key)
if all((value, validator)):
validator(value, module)
return value
def map_key_params_to_dict(keys):
"""
Map the list of keys to a dictionary of dictionaries
indexed by key name
"""
all_keys = dict()
if keys is None:
return all_keys
for key in keys:
key_name = key["name"]
all_keys[key_name] = key
return all_keys
def map_params_to_obj(module):
aggregate = module.params["aggregate"]
if not aggregate:
if not module.params["name"] and module.params["purge"]:
return list()
else:
users = [{"name": module.params["name"]}]
else:
users = list()
for item in aggregate:
if not isinstance(item, dict):
users.append({"name": item})
else:
users.append(item)
objects = list()
for item in users:
get_value = partial(get_param_value, item=item, module=module)
item["configured_password"] = get_value("configured_password")
item["encrypted_password"] = get_value("encrypted_password")
item["full_name"] = get_value("full_name")
item["state"] = get_value("state")
item["public_keys"] = map_key_params_to_dict(get_value("public_keys"))
objects.append(item)
return objects
def update_objects(want, have):
updates = list()
for entry in want:
item = next((i for i in have if i["name"] == entry["name"]), None)
if item is None:
updates.append((entry, {}))
elif item:
- for key, value in iteritems(entry):
+ for key, value in entry.items():
if value and value != item[key]:
updates.append((entry, item))
return updates
def main():
"""main entry point for module execution"""
public_key_spec = dict(
name=dict(required=True, type="str"),
key=dict(required=True, type="str", no_log=False),
type=dict(
required=True,
type="str",
choices=[
"ssh-dss",
"ssh-rsa",
"ecdsa-sha2-nistp256",
"ecdsa-sha2-nistp384",
"ssh-ed25519",
"ecdsa-sha2-nistp521",
],
),
)
element_spec = dict(
name=dict(),
full_name=dict(),
configured_password=dict(no_log=True),
encrypted_password=dict(no_log=False),
update_password=dict(default="always", choices=["on_create", "always"]),
state=dict(default="present", choices=["present", "absent"]),
public_keys=dict(type="list", elements="dict", options=public_key_spec),
)
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,
aliases=["users", "collection"],
),
purge=dict(type="bool", default=False),
)
argument_spec.update(element_spec)
mutually_exclusive = [
("name", "aggregate"),
("encrypted_password", "configured_password"),
]
module = AnsibleModule(
argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True,
)
warnings = list()
result = {"changed": False, "warnings": warnings}
want = map_params_to_obj(module)
have = config_to_dict(module)
commands = spec_to_commands(update_objects(want, have), module)
if module.params["purge"]:
want_users = [x["name"] for x in want]
have_users = [x["name"] for x in have]
for item in set(have_users).difference(want_users):
commands.append("delete system login user %s" % item)
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/tests/sanity/ignore-2.21.txt b/tests/sanity/ignore-2.21.txt
new file mode 100644
index 00000000..c835eef8
--- /dev/null
+++ b/tests/sanity/ignore-2.21.txt
@@ -0,0 +1 @@
+plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local`
diff --git a/tests/unit/mock/procenv.py b/tests/unit/mock/procenv.py
index d7f3dc95..d21e2a0e 100644
--- a/tests/unit/mock/procenv.py
+++ b/tests/unit/mock/procenv.py
@@ -1,97 +1,96 @@
# (c) 2016, Matt Davis <mdavis@ansible.com>
# (c) 2016, Toshio Kuratomi <tkuratomi@ansible.com>
#
# This file is part of Ansible
#
# 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 <http://www.gnu.org/licenses/>.
# Make coding more python3-ish
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import json
import sys
from contextlib import contextmanager
from io import BytesIO, StringIO
from ansible.module_utils._text import to_bytes
-from ansible.module_utils.six import PY3
from ansible_collections.vyos.vyos.tests.unit.compat import unittest
@contextmanager
def swap_stdin_and_argv(stdin_data="", argv_data=tuple()):
"""
context manager that temporarily masks the test runner's values for stdin and argv
"""
real_stdin = sys.stdin
real_argv = sys.argv
- if PY3:
- fake_stream = StringIO(stdin_data)
- fake_stream.buffer = BytesIO(to_bytes(stdin_data))
- else:
- fake_stream = BytesIO(to_bytes(stdin_data))
+ # if PY3:
+ fake_stream = StringIO(stdin_data)
+ fake_stream.buffer = BytesIO(to_bytes(stdin_data))
+ # else:
+ # fake_stream = BytesIO(to_bytes(stdin_data))
try:
sys.stdin = fake_stream
sys.argv = argv_data
yield
finally:
sys.stdin = real_stdin
sys.argv = real_argv
@contextmanager
def swap_stdout():
"""
context manager that temporarily replaces stdout for tests that need to verify output
"""
old_stdout = sys.stdout
- if PY3:
- fake_stream = StringIO()
- else:
- fake_stream = BytesIO()
+ # if PY3:
+ fake_stream = StringIO()
+ # else:
+ # fake_stream = BytesIO()
try:
sys.stdout = fake_stream
yield fake_stream
finally:
sys.stdout = old_stdout
class ModuleTestCase(unittest.TestCase):
def setUp(self, module_args=None):
if module_args is None:
module_args = {
"_ansible_remote_tmp": "/tmp",
"_ansible_keep_remote_files": False,
}
args = json.dumps(dict(ANSIBLE_MODULE_ARGS=module_args))
# unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually
self.stdin_swap = swap_stdin_and_argv(stdin_data=args)
self.stdin_swap.__enter__()
def tearDown(self):
# unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually
self.stdin_swap.__exit__(None, None, None)
diff --git a/tests/unit/mock/yaml_helper.py b/tests/unit/mock/yaml_helper.py
index 2e857592..2cfd7d65 100644
--- a/tests/unit/mock/yaml_helper.py
+++ b/tests/unit/mock/yaml_helper.py
@@ -1,157 +1,124 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import io
import yaml
-from ansible.module_utils.six import PY3
from ansible.parsing.yaml.dumper import AnsibleDumper
from ansible.parsing.yaml.loader import AnsibleLoader
class YamlTestUtils(object):
"""Mixin class to combine with a unittest.TestCase subclass."""
def _loader(self, stream):
"""Vault related tests will want to override this.
Vault cases should setup a AnsibleLoader that has the vault password."""
return AnsibleLoader(stream)
def _dump_stream(self, obj, stream, dumper=None):
"""Dump to a py2-unicode or py3-string stream."""
- if PY3:
- return yaml.dump(obj, stream, Dumper=dumper)
- else:
- return yaml.dump(obj, stream, Dumper=dumper, encoding=None)
+ return yaml.dump(obj, stream, Dumper=dumper)
def _dump_string(self, obj, dumper=None):
"""Dump to a py2-unicode or py3-string"""
- if PY3:
- return yaml.dump(obj, Dumper=dumper)
- else:
- return yaml.dump(obj, Dumper=dumper, encoding=None)
+ return yaml.dump(obj, Dumper=dumper)
def _dump_load_cycle(self, obj):
# Each pass though a dump or load revs the 'generation'
# obj to yaml string
string_from_object_dump = self._dump_string(obj, dumper=AnsibleDumper)
# wrap a stream/file like StringIO around that yaml
stream_from_object_dump = io.StringIO(string_from_object_dump)
loader = self._loader(stream_from_object_dump)
# load the yaml stream to create a new instance of the object (gen 2)
obj_2 = loader.get_data()
# dump the gen 2 objects directory to strings
string_from_object_dump_2 = self._dump_string(obj_2, dumper=AnsibleDumper)
# The gen 1 and gen 2 yaml strings
self.assertEqual(string_from_object_dump, string_from_object_dump_2)
# the gen 1 (orig) and gen 2 py object
self.assertEqual(obj, obj_2)
# again! gen 3... load strings into py objects
stream_3 = io.StringIO(string_from_object_dump_2)
loader_3 = self._loader(stream_3)
obj_3 = loader_3.get_data()
string_from_object_dump_3 = self._dump_string(obj_3, dumper=AnsibleDumper)
self.assertEqual(obj, obj_3)
# should be transitive, but...
self.assertEqual(obj_2, obj_3)
self.assertEqual(string_from_object_dump, string_from_object_dump_3)
def _old_dump_load_cycle(self, obj):
"""Dump the passed in object to yaml, load it back up, dump again, compare."""
stream = io.StringIO()
yaml_string = self._dump_string(obj, dumper=AnsibleDumper)
self._dump_stream(obj, stream, dumper=AnsibleDumper)
yaml_string_from_stream = stream.getvalue()
# reset stream
stream.seek(0)
loader = self._loader(stream)
# loader = AnsibleLoader(stream, vault_password=self.vault_password)
obj_from_stream = loader.get_data()
stream_from_string = io.StringIO(yaml_string)
loader2 = self._loader(stream_from_string)
# loader2 = AnsibleLoader(stream_from_string, vault_password=self.vault_password)
obj_from_string = loader2.get_data()
stream_obj_from_stream = io.StringIO()
stream_obj_from_string = io.StringIO()
- if PY3:
- yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper)
- yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper)
- else:
- yaml.dump(
- obj_from_stream,
- stream_obj_from_stream,
- Dumper=AnsibleDumper,
- encoding=None,
- )
- yaml.dump(
- obj_from_stream,
- stream_obj_from_string,
- Dumper=AnsibleDumper,
- encoding=None,
- )
+ yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper)
+ yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper)
yaml_string_stream_obj_from_stream = stream_obj_from_stream.getvalue()
yaml_string_stream_obj_from_string = stream_obj_from_string.getvalue()
stream_obj_from_stream.seek(0)
stream_obj_from_string.seek(0)
- if PY3:
- yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper)
- yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper)
- else:
- yaml_string_obj_from_stream = yaml.dump(
- obj_from_stream,
- Dumper=AnsibleDumper,
- encoding=None,
- )
- yaml_string_obj_from_string = yaml.dump(
- obj_from_string,
- Dumper=AnsibleDumper,
- encoding=None,
- )
+ yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper)
+ yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper)
assert yaml_string == yaml_string_obj_from_stream
assert yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string
assert (
yaml_string
== yaml_string_obj_from_stream
== yaml_string_obj_from_string
== yaml_string_stream_obj_from_stream
== yaml_string_stream_obj_from_string
)
assert obj == obj_from_stream
assert obj == obj_from_string
assert obj == yaml_string_obj_from_stream
assert obj == yaml_string_obj_from_string
assert (
obj
== obj_from_stream
== obj_from_string
== yaml_string_obj_from_stream
== yaml_string_obj_from_string
)
return {
"obj": obj,
"yaml_string": yaml_string,
"yaml_string_from_stream": yaml_string_from_stream,
"obj_from_stream": obj_from_stream,
"obj_from_string": obj_from_string,
"yaml_string_obj_from_string": yaml_string_obj_from_string,
}
diff --git a/tests/unit/modules/conftest.py b/tests/unit/modules/conftest.py
index 41465c30..0f36839e 100644
--- a/tests/unit/modules/conftest.py
+++ b/tests/unit/modules/conftest.py
@@ -1,32 +1,31 @@
# Copyright (c) 2017 Ansible Project
# 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
import json
import pytest
from ansible.module_utils._text import to_bytes
from ansible.module_utils.common._collections_compat import MutableMapping
-from ansible.module_utils.six import string_types
@pytest.fixture
def patch_ansible_module(request, mocker):
- if isinstance(request.param, string_types):
+ if isinstance(request.param, str):
args = request.param
elif isinstance(request.param, MutableMapping):
if "ANSIBLE_MODULE_ARGS" not in request.param:
request.param = {"ANSIBLE_MODULE_ARGS": request.param}
if "_ansible_remote_tmp" not in request.param["ANSIBLE_MODULE_ARGS"]:
request.param["ANSIBLE_MODULE_ARGS"]["_ansible_remote_tmp"] = "/tmp"
if "_ansible_keep_remote_files" not in request.param["ANSIBLE_MODULE_ARGS"]:
request.param["ANSIBLE_MODULE_ARGS"]["_ansible_keep_remote_files"] = False
args = json.dumps(request.param)
else:
raise Exception("Malformed data to the patch_ansible_module pytest fixture")
mocker.patch("ansible.module_utils.basic._ANSIBLE_ARGS", to_bytes(args))

File Metadata

Mime Type
text/x-diff
Expires
Sat, Oct 17, 12:19 AM (18 h, 41 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4416526
Default Alt Text
(479 KB)

Event Timeline