diff options
Diffstat (limited to 'ansible_collections/cisco/ios/plugins/module_utils')
33 files changed, 1037 insertions, 1439 deletions
diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/bgp_global/bgp_global.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/bgp_global/bgp_global.py index 21bb051c3..6e82c99c2 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/bgp_global/bgp_global.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/bgp_global/bgp_global.py @@ -163,6 +163,20 @@ class Bgp_globalArgs(object): # pylint: disable=R0903 "route_map": {"type": "str"}, }, }, + "default": { + "type": "dict", + "apply_defaults": True, + "options": { + "ipv4_unicast": {"type": "bool", "default": True}, + "route_target": { + "type": "dict", + "apply_defaults": True, + "options": { + "filter": {"type": "bool", "default": True}, + }, + }, + }, + }, "deterministic_med": {"type": "bool"}, "dmzlink_bw": {"type": "bool"}, "enforce_first_as": {"type": "bool"}, diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/l3_interfaces/l3_interfaces.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/l3_interfaces/l3_interfaces.py index 455261409..82ae98ee6 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/l3_interfaces/l3_interfaces.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/l3_interfaces/l3_interfaces.py @@ -36,6 +36,8 @@ class L3_interfacesArgs(object): # pylint: disable=R0903 "type": "list", "elements": "dict", "options": { + "autostate": {"type": "bool"}, + "mac_address": {"type": "str"}, "name": {"type": "str", "required": True}, "ipv4": { "type": "list", @@ -54,6 +56,14 @@ class L3_interfacesArgs(object): # pylint: disable=R0903 }, }, "pool": {"type": "str"}, + "source_interface": { + "type": "dict", + "options": { + "name": {"type": "str"}, + "poll": {"type": "bool"}, + "point_to_point": {"type": "bool"}, + }, + }, }, }, "ipv6": { @@ -72,6 +82,7 @@ class L3_interfacesArgs(object): # pylint: disable=R0903 "rapid_commit": {"type": "bool"}, }, }, + "enable": {"type": "bool"}, "anycast": {"type": "bool"}, "cga": {"type": "bool"}, "eui": {"type": "bool"}, diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/vlans/vlans.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/vlans/vlans.py index 9db593dcc..65e631817 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/vlans/vlans.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/vlans/vlans.py @@ -51,7 +51,10 @@ class VlansArgs(object): "private_vlan": { "type": "dict", "options": { - "type": {"type": "str", "choices": ["primary", "community", "isolated"]}, + "type": { + "type": "str", + "choices": ["primary", "community", "isolated"], + }, "associated": {"type": "list", "elements": "int"}, }, }, @@ -65,7 +68,6 @@ class VlansArgs(object): }, "type": "list", }, - "configuration": {"type": "bool"}, "running_config": {"type": "str"}, "state": { "choices": [ @@ -75,6 +77,7 @@ class VlansArgs(object): "deleted", "rendered", "parsed", + "purged", "gathered", ], "default": "merged", diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/acls/acls.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/acls/acls.py index 164c75c40..f604e4360 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/acls/acls.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/acls/acls.py @@ -119,6 +119,16 @@ class Acls(ResourceModule): for wname, wentry in iteritems(wplists): hentry = hplists.pop(wname, {}) acl_type = wentry["acl_type"] if wentry.get("acl_type") else hentry.get("acl_type") + # If ACLs type is different between existing and wanted ACL, we need first remove it + if acl_type != hentry.get("acl_type", acl_type): + self.commands.append( + "no " + self.acl_name_cmd(wname, afi, hentry.get("acl_type", "")), + ) + hentry.pop( + "aces", + {}, + ) # We remove ACEs because we have previously add a command to suppress completely the ACL + begin = len(self.commands) # to determine the index for acl command self._compare_aces( wentry.pop("aces", {}), @@ -159,20 +169,22 @@ class Acls(ResourceModule): else: return {} + # case 1 - loop on want and compare with have data here for wseq, wentry in iteritems(want): hentry = have.pop(wseq, {}) rem_hentry, rem_wentry = {}, {} - if hentry: + if hentry: # if there is have information with same sequence + # the protocol options are processed here hentry = self.sanitize_protocol_options(wentry, hentry) - if hentry != wentry: # will let in if ace is same but remarks is not same - if hentry: + if hentry != wentry: # if want and have is different + if hentry: # separate remarks from have in an ace entry rem_hentry["remarks"] = pop_remark(hentry, afi) - if wentry: + if wentry: # separate remarks from want in an ace entry rem_wentry["remarks"] = pop_remark(wentry, afi) - if hentry: + if hentry: # have aces processing starts here if self.state == "merged": self._module.fail_json( msg="Cannot update existing sequence {0} of ACLs {1} with state merged." @@ -180,55 +192,78 @@ class Acls(ResourceModule): hentry.get("sequence", ""), name, ), - ) - else: # other action states - if rem_hentry.get("remarks"): # remove remark if not in want - for k_hrems, hrems in rem_hentry.get("remarks").items(): - if k_hrems not in rem_wentry.get("remarks", {}).keys(): - if self.state in ["replaced", "overridden"]: - self.addcmd( - { - "remarks": hrems, - "sequence": hentry.get("sequence", ""), - }, - "remarks_no_data", - negate=True, - ) - break - else: - self.addcmd( - { - "remarks": hrems, - "sequence": hentry.get("sequence", ""), - }, - "remarks", - negate=True, - ) - # remove ace if not in want - if hentry != wentry: - self.addcmd(add_afi(hentry, afi), "aces", negate=True) + ) # if merged then don't update anything and fail + + # i.e if not merged + if rem_hentry.get("remarks") != rem_wentry.get("remarks"): + self.addcmd( + { + "sequence": hentry.get("sequence", None), + }, + "remarks_no_data", + negate=True, + ) # remove all remarks for a ace if want and have don't match + # as if we randomly add aces we cannot maintain order we have to + # add all of them again, for that ace + rem_hentry["remarks"] = {} + # and me empty our have as we would add back + # all our remarks for that ace anyways + + # remove ace if not in want + # we might think why not update it directly, + # if we try to update without negating the entry appliance + # reports % Duplicate sequence number + if hentry != wentry: + self.addcmd(add_afi(hentry, afi), "aces", negate=True) + # once an ace is negated intentionally emptying out have so that + # the remarks are repopulated, as the remarks and ace behavior is sticky + # if an ace is taken out all the remarks is removed automatically. + rem_hentry["remarks"] = {} + if rem_wentry.get("remarks"): # add remark if not in have + if rem_hentry.get("remarks"): + self.addcmd( + { + "sequence": hentry.get("sequence", None), + }, + "remarks_no_data", + negate=True, + ) # but delete all remarks before to protect order for k_wrems, wrems in rem_wentry.get("remarks").items(): - if k_wrems not in rem_hentry.get("remarks", {}).keys(): - self.addcmd( - {"remarks": wrems, "sequence": hentry.get("sequence", "")}, - "remarks", - ) + self.addcmd( + { + "remarks": wrems, + "sequence": wentry.get("sequence", ""), + }, + "remarks", + ) + # add ace if not in have if hentry != wentry: - self.addcmd(add_afi(wentry, afi), "aces") - - # remove remaining entries from have aces list + if len(wentry) == 1 and wentry.get( + "sequence", + ): # if the ace entry just has sequence then do nothing + continue + else: # add normal ace entries from want + self.addcmd(add_afi(wentry, afi), "aces") + + # case 2 - loop over remaining have and remove them for hseq in have.values(): - if hseq.get("remarks"): # remove remarks that are extra in have - for krems, rems in hseq.get("remarks").items(): - self.addcmd( - {"remarks": rems, "sequence": hseq.get("sequence", "")}, - "remarks", - negate=True, - ) - else: # remove extra aces - self.addcmd(add_afi(hseq, afi), "aces", negate=True) + if hseq.get("remarks"): # remove all remarks in that + self.addcmd( + { + "sequence": hseq.get("sequence", None), + }, + "remarks_no_data", + negate=True, + ) + hseq.pop("remarks") + # deal with the rest of ace entry + self.addcmd( + add_afi(hseq, afi), + "aces", + negate=True, + ) def sanitize_protocol_options(self, wace, hace): """handles protocol and protocol options as optional attribute""" @@ -262,7 +297,7 @@ class Acls(ResourceModule): def list_to_dict(self, param): """converts list attributes to dict""" - temp, count = dict(), 0 + temp = dict() if param: for each in param: # ipv4 and ipv6 acl temp_acls = {} @@ -271,7 +306,9 @@ class Acls(ResourceModule): temp_aces = {} if acl.get("aces"): rem_idx = 0 # remarks if defined in an ace - for ace in acl.get("aces"): # each ace turned to dict + for count, ace in enumerate( + acl.get("aces"), + ): # each ace turned to dict if ( ace.get("destination") and ace.get("destination", {}).get( @@ -285,9 +322,9 @@ class Acls(ResourceModule): for k, v in ( ace.get("destination", {}).get("port_protocol", {}).items() ): - ace["destination"]["port_protocol"][ - k - ] = self.port_protocl_no_to_protocol(v) + ace["destination"]["port_protocol"][k] = ( + self.port_protocl_no_to_protocol(v) + ) if acl.get("acl_type") == "standard": for ks in list(ace.keys()): if ks not in [ @@ -311,13 +348,14 @@ class Acls(ResourceModule): # temp_rem.extend(ace.pop("remarks")) for remks in ace.get("remarks"): rem_ace[remks.replace(" ", "_")] = remks - rem_idx += 1 ace["remarks"] = rem_ace if ace.get("sequence"): temp_aces.update({ace.get("sequence"): ace}) + elif ace.get("remarks"): + temp_aces.update({"__{0}".format(rem_idx): ace}) + rem_idx += 1 elif ace: - count += 1 temp_aces.update({"_" + to_text(count): ace}) # if temp_rem: # add remarks to the temp ace @@ -325,7 +363,12 @@ class Acls(ResourceModule): if acl.get("acl_type"): # update acl dict with req info temp_acls.update( - {acl.get("name"): {"aces": temp_aces, "acl_type": acl["acl_type"]}}, + { + acl.get("name"): { + "aces": temp_aces, + "acl_type": acl["acl_type"], + }, + }, ) else: # if no acl type then here eg: ipv6 temp_acls.update({acl.get("name"): {"aces": temp_aces}}) diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/bgp_global/bgp_global.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/bgp_global/bgp_global.py index c48cac946..77112ffa2 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/bgp_global/bgp_global.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/bgp_global/bgp_global.py @@ -61,6 +61,8 @@ class Bgp_global(ResourceModule): "route_server_context.description", "synchronization", "table_map", + "template.peer_policy", + "template.peer_session", "timers", "bgp.additional_paths", "bgp.advertise_best_external", @@ -79,6 +81,8 @@ class Bgp_global(ResourceModule): "bgp.consistency_checker.auto_repair", "bgp.consistency_checker.error_message", "bgp.dampening", + "bgp.default.ipv4_unicast", + "bgp.default.route_target.filter", "bgp.deterministic_med", "bgp.dmzlink_bw", "bgp.enforce_first_as", @@ -168,7 +172,10 @@ class Bgp_global(ResourceModule): if self.state == "deleted": # deleted, clean up global params if not self.want or (self.have.get("as_number") == self.want.get("as_number")): - self._compare(want={}, have=self.have) + if "as_number" not in self.want: + self.want["as_number"] = self.have.get("as_number") + self._set_bgp_defaults(self.want) + self._compare(self.want, have=self.have) elif self.state == "purged": # delete as_number takes down whole bgp config @@ -190,10 +197,16 @@ class Bgp_global(ResourceModule): for the Bgp_global network resource. """ self.generic_list_parsers = ["distributes", "aggregate_addresses", "networks"] + if self._has_bgp_inject_maps(want): self.generic_list_parsers.insert(0, "inject_maps") cmd_len = len(self.commands) # holds command length to add as_number + + # for clean bgp global setup + if not have.get("bgp", {}).get("default"): + self._set_bgp_defaults(have) + # for dict type attributes self.compare(parsers=self.parsers, want=want, have=have) @@ -206,17 +219,40 @@ class Bgp_global(ResourceModule): _parse, ) else: - self._compare_generic_lists(want.get(_parse, {}), have.get(_parse, {}), _parse) + self._compare_generic_lists( + want.get(_parse, {}), + have.get(_parse, {}), + _parse, + ) # for neighbors - self._compare_neighbor_lists(want.get("neighbors", {}), have.get("neighbors", {})) + self._compare_neighbor_lists( + want.get("neighbors", {}), + have.get("neighbors", {}), + ) # for redistribute - self._compare_redistribute_lists(want.get("redistribute", {}), have.get("redistribute", {})) + self._compare_redistribute_lists( + want.get("redistribute", {}), + have.get("redistribute", {}), + ) - # add as_number in the begining fo command set if commands generated + # add as_number in the beginning of commands set if commands generated if len(self.commands) != cmd_len or (not have and want): - self.commands.insert(0, self._tmplt.render(want or have, "as_number", False)) + self.commands.insert( + 0, + self._tmplt.render(want or have, "as_number", False), + ) + + def _set_bgp_defaults(self, bgp_dict): + bgp_dict.setdefault("bgp", {}).setdefault("default", {}).setdefault( + "ipv4_unicast", + True, + ) + bgp_dict.setdefault("bgp", {}).setdefault("default", {}).setdefault( + "route_target", + {}, + ).setdefault("filter", True) def _has_bgp_inject_maps(self, want): if want.get("bgp", {}).get("inject_maps", {}): @@ -324,10 +360,21 @@ class Bgp_global(ResourceModule): ] for name, w_neighbor in want.items(): + handle_shutdown_default = False have_nbr = have.pop(name, {}) want_route = w_neighbor.pop("route_maps", {}) have_route = have_nbr.pop("route_maps", {}) + if ( + not w_neighbor.get("shutdown", {}).get("set") + and have_nbr.get("shutdown", {}).get("set") + and self.state in ["merged", "replaced", "overridden"] + ): + neig_parses.remove("shutdown") + handle_shutdown_default = True self.compare(parsers=neig_parses, want=w_neighbor, have=have_nbr) + if handle_shutdown_default: + self.addcmd(have_nbr, "shutdown", True) + if want_route: for k_rmps, w_rmps in want_route.items(): have_rmps = have_route.pop(k_rmps, {}) diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/l3_interfaces/l3_interfaces.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/l3_interfaces/l3_interfaces.py index b4b874d6e..b862ac466 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/l3_interfaces/l3_interfaces.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/l3_interfaces/l3_interfaces.py @@ -29,7 +29,6 @@ from ansible_collections.cisco.ios.plugins.module_utils.network.ios.rm_templates ) from ansible_collections.cisco.ios.plugins.module_utils.network.ios.utils.utils import ( normalize_interface, - validate_ipv6, validate_n_expand_ipv4, ) @@ -48,12 +47,18 @@ class L3_interfaces(ResourceModule): tmplt=L3_interfacesTemplate(), ) self.parsers = [ + "mac_address", "ipv4.address", "ipv4.pool", "ipv4.dhcp", + "ipv4.source_interface", "ipv6.address", "ipv6.autoconfig", "ipv6.dhcp", + "ipv6.enable", + ] + self.gen_parsers = [ + "autostate", ] def execute_module(self): @@ -101,10 +106,16 @@ class L3_interfaces(ResourceModule): self._compare(want={}, have=have) for k, want in wantd.items(): - self._compare(want=want, have=haved.pop(k, {})) + have = haved.pop(k, {}) + # New interface (doesn't use fact file) + if k[:4] == "Vlan": + have.setdefault("autostate", True) + want.setdefault("autostate", True) + self._compare(want=want, have=have) def _compare(self, want, have): begin = len(self.commands) + self.compare(parsers=self.gen_parsers, want=want, have=have) self._compare_lists(want=want, have=have) if len(self.commands) != begin: self.commands.insert(begin, self._tmplt.render(want or have, "name", False)) @@ -141,7 +152,12 @@ class L3_interfaces(ResourceModule): # hacl is set as primary, if wacls has no other primary entry we must keep # this entry as primary (so we'll compare entry to hacl and not # generate commands) - if list(filter(lambda w: w.get("secondary", False) is False, wacls.values())): + if list( + filter( + lambda w: w.get("secondary", False) is False, + wacls.values(), + ), + ): # another primary is in wacls hacl = {} self.validate_ips(afi, want=entry, have=hacl) @@ -168,17 +184,11 @@ class L3_interfaces(ResourceModule): v4_addr = validate_n_expand_ipv4(self._module, want) if want.get("address") else {} if v4_addr: want["address"] = v4_addr - elif afi == "ipv6" and want: - if want.get("address"): - validate_ipv6(want["address"], self._module) if afi == "ipv4" and have: v4_addr_h = validate_n_expand_ipv4(self._module, have) if have.get("address") else {} if v4_addr_h: have["address"] = v4_addr_h - elif afi == "ipv6" and have: - if have.get("address"): - validate_ipv6(have["address"], self._module) def list_to_dict(self, param): if param: diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/route_maps/route_maps.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/route_maps/route_maps.py index d8a1bec9f..f02fbfdfb 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/route_maps/route_maps.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/route_maps/route_maps.py @@ -304,11 +304,11 @@ class Route_maps(ResourceModule): "acl", ) elif match["ip"][each_ip_param].get("prefix_lists"): - match["ip"][each_ip_param][ - "prefix_lists" - ] = convert_to_dict( - match["ip"][each_ip_param]["prefix_lists"], - "prefix_list", + match["ip"][each_ip_param]["prefix_lists"] = ( + convert_to_dict( + match["ip"][each_ip_param]["prefix_lists"], + "prefix_list", + ) ) if match.get("local_preference") and match.get("local_preference").get( "value", diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/snmp_server/snmp_server.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/snmp_server/snmp_server.py index 187d0779d..d61a70a10 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/snmp_server/snmp_server.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/snmp_server/snmp_server.py @@ -237,39 +237,20 @@ class Snmp_server(ResourceModule): def _compare_lists_attrs(self, want, have): """Compare list of dict""" for _parser in self.list_parsers: - if _parser == "users": - i_want = want.get(_parser, {}) - i_have = have.get(_parser, {}) - for key, wanting in iteritems(i_want): - wanting_compare = deepcopy(wanting) - if ( - "authentication" in wanting_compare - and "password" in wanting_compare["authentication"] - ): - wanting_compare["authentication"].pop("password") - if ( - "encryption" in wanting_compare - and "password" in wanting_compare["encryption"] - ): - wanting_compare["encryption"].pop("password") - haveing = i_have.pop(key, {}) - if wanting_compare != haveing: - if haveing and self.state in ["overridden", "replaced"]: + i_want = want.get(_parser, {}) + i_have = have.get(_parser, {}) + for key, wanting in iteritems(i_want): + haveing = i_have.pop(key, {}) + if wanting != haveing: + if haveing and self.state in ["overridden", "replaced"]: + if not ( + _parser == "users" + and wanting.get("username") == haveing.get("username") + ): self.addcmd(haveing, _parser, negate=True) - self.addcmd(wanting, _parser) - for key, haveing in iteritems(i_have): - self.addcmd(haveing, _parser, negate=True) - else: - i_want = want.get(_parser, {}) - i_have = have.get(_parser, {}) - for key, wanting in iteritems(i_want): - haveing = i_have.pop(key, {}) - if wanting != haveing: - if haveing and self.state in ["overridden", "replaced"]: - self.addcmd(haveing, _parser, negate=True) - self.addcmd(wanting, _parser) - for key, haveing in iteritems(i_have): - self.addcmd(haveing, _parser, negate=True) + self.addcmd(wanting, _parser) + for key, haveing in iteritems(i_have): + self.addcmd(haveing, _parser, negate=True) def _snmp_list_to_dict(self, data): """Convert all list of dicts to dicts of dicts""" diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/vlans/vlans.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/vlans/vlans.py index ad95b680a..d195c317e 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/vlans/vlans.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/vlans/vlans.py @@ -1,62 +1,60 @@ # # -*- coding: utf-8 -*- -# Copyright 2019 Red Hat +# Copyright 2024 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -""" -The ios_vlans 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 +""" +The ios_vlans 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_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( - ConfigBase, +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 ( - remove_empties, - to_list, + dict_merge, ) from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.facts import Facts -from ansible_collections.cisco.ios.plugins.module_utils.network.ios.utils.utils import dict_to_set +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.rm_templates.vlans import ( + VlansTemplate, +) -class Vlans(ConfigBase): +class Vlans(ResourceModule): """ - The ios_vlans class + The ios_vlans config class """ - gather_subset = ["!all", "!min"] - - gather_network_resources = ["vlans"] - def __init__(self, module): - super(Vlans, self).__init__(module) - - def get_vlans_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, + super(Vlans, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="vlans", + tmplt=VlansTemplate(), ) - vlans_facts = facts["ansible_network_resources"].get("vlans") - if not vlans_facts: - return [] - return vlans_facts + self.parsers = [ + "name", + "state", + "mtu", + "remote_span", + "private_vlan.type", + "private_vlan.associated", + "member", + ] def execute_module(self): """Execute the module @@ -64,394 +62,106 @@ class Vlans(ConfigBase): :rtype: A dictionary :returns: The result from module execution """ - result = {"changed": False} - commands = list() - warnings = list() - self.have_now = list() - self.configuration = self._module.params["configuration"] - if not self.configuration: - self.vlan_parent = "vlan {0}" - else: - self.vlan_parent = "vlan configuration {0}" - if self.state in self.ACTION_STATES: - existing_vlans_facts = self.get_vlans_facts() - else: - existing_vlans_facts = [] - - if self.state in self.ACTION_STATES or self.state == "rendered": - commands.extend(self.set_config(existing_vlans_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_vlans_facts = self.get_vlans_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", + if self.state not in ["parsed", "gathered"]: + self.segregate_resource() + self.run_commands() + return self.result + + def segregate_resource(self): + self.want_vlan_config = [] + self.have_vlan_config = [] + for vlan_data in self.want: + if vlan_data.get("member"): + self.want_vlan_config.append( + { + "vlan_id": vlan_data.get("vlan_id"), + "member": vlan_data.pop("member"), + }, ) - result["parsed"] = self.get_vlans_facts(data=running_config) - else: - changed_vlans_facts = [] - - if self.state in self.ACTION_STATES: - result["before"] = existing_vlans_facts - if result["changed"]: - result["after"] = changed_vlans_facts - elif self.state == "gathered": - result["gathered"] = changed_vlans_facts - - result["warnings"] = warnings - return result - - def set_config(self, existing_vlans_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 = [] - if self._module.params.get("config"): - for cfg in self._module.params["config"]: - want.append(remove_empties(cfg)) - have = existing_vlans_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 - """ - - if self.state in ("overridden", "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 == "overridden": - commands = self._state_overridden(want, have) - elif self.state == "deleted": - commands = self._state_deleted(want, have) - elif self.state in ("merged", "rendered"): - commands = self._state_merged(want, have) - elif self.state == "replaced": - commands = self._state_replaced(want, 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 = [] - - check = False - for each in want: - for every in have: - if every["vlan_id"] == each["vlan_id"]: - check = True - break - continue - if check: - commands.extend(self._set_config(each, every)) - else: - commands.extend(self._set_config(each, dict())) - - 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 = [] - - want_local = want - self.have_now = have.copy() - for each in have: - count = 0 - for every in want_local: - if each["vlan_id"] == every["vlan_id"]: - break - count += 1 else: - # We didn't find a matching desired state, which means we can - # pretend we received an empty desired state. - commands.extend(self._clear_config(every, each)) - continue - commands.extend(self._set_config(every, each)) - # as the pre-existing VLAN are now configured by - # above set_config call, deleting the respective - # VLAN entry from the want_local list - del want_local[count] - - # Iterating through want_local list which now only have new VLANs to be - # configured - for each in want_local: - commands.extend(self._set_config(each, dict())) - - 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 = [] - - check = False - for each in want: - for every in have: - if each.get("vlan_id") == every.get("vlan_id"): - check = True - break - continue - if check: - commands.extend(self._set_config(each, every)) + self.want_vlan_config.append( + {"vlan_id": vlan_data.get("vlan_id")}, + ) + for vlan_data in self.have: + if vlan_data.get("member"): + self.have_vlan_config.append( + { + "vlan_id": vlan_data.get("vlan_id"), + "member": vlan_data.pop("member"), + }, + ) else: - commands.extend(self._set_config(each, dict())) - - return commands - - def _state_deleted(self, want, have): - """The command generator when state is deleted + self.have_vlan_config.append( + {"vlan_id": vlan_data.get("vlan_id")}, + ) + if self.want or self.have: + self.generate_commands(self.want, self.have, "vlans") + if self.want_vlan_config or self.have_vlan_config: + self.generate_commands( + self.want_vlan_config, + self.have_vlan_config, + "vlan_configuration", + ) - :rtype: A list - :returns: the commands necessary to remove the current configuration - of the provided objects + def generate_commands(self, conf_want, conf_have, resource=None): + """Generate configuration commands to send based on + want, have and desired state. """ - commands = [] - - if want: - check = False - for each in want: - for every in have: - if each.get("vlan_id") == every.get("vlan_id"): - check = True - break - check = False - continue - if check: - commands.extend(self._clear_config(each, every)) + wantd = {entry["vlan_id"]: entry for entry in conf_want} + haved = {entry["vlan_id"]: entry for entry in conf_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} + wantd = {} + + # if state is deleted, empty out wantd and set haved to wantd + if self.state in ["deleted", "purged"]: + haved = {k: v for k, v in iteritems(haved) 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): + if k not in wantd: + self._compare(want={}, have=have, resource=resource) + + if self.state == "purged": + for k, have in iteritems(haved): + self.purge(have, resource) else: - for each in have: - commands.extend(self._clear_config(dict(), each)) - - return commands - - def remove_command_from_config_list(self, vlan_id, cmd, commands): - if vlan_id not in commands and cmd != "vlan": - commands.insert(0, vlan_id) - elif cmd == "vlan": - commands.append("no %s" % vlan_id) - return commands - commands.append("no %s" % cmd) - return commands - - def add_command_to_config_list(self, vlan_id, cmd, commands): - if vlan_id not in commands: - commands.insert(0, vlan_id) - if cmd not in commands: - commands.append(cmd) - - def _set_config(self, want, have): - # Set the interface config based on the want and have config - commands = [] - - vlan = self.vlan_parent.format(want.get("vlan_id")) - - def negate_have_config(want_diff, have_diff, vlan, commands): - name = dict(have_diff).get("name") - if name and not dict(want_diff).get("name"): - self.remove_command_from_config_list(vlan, "name {0}".format(name), commands) - state = dict(have_diff).get("state") - if state and not dict(want_diff).get("state"): - self.remove_command_from_config_list(vlan, "state {0}".format(state), commands) - shutdown = dict(have_diff).get("shutdown") - if shutdown and not dict(want_diff).get("shutdown"): - self.remove_command_from_config_list(vlan, "shutdown", commands) - mtu = dict(have_diff).get("mtu") - if mtu and not dict(want_diff).get("mtu"): - self.remove_command_from_config_list(vlan, "mtu {0}".format(mtu), commands) - remote_span = dict(have_diff).get("remote_span") - if remote_span and not dict(want_diff).get("remote_span"): - self.remove_command_from_config_list(vlan, "remote-span", commands) - private_vlan = dict(have_diff).get("private_vlan") - if private_vlan and not dict(want_diff).get("private_vlan"): - private_vlan_type = dict(private_vlan).get("type") - self.remove_command_from_config_list( - vlan, - "private-vlan {0}".format(private_vlan_type), - commands, - ) - if private_vlan_type == "primary" and dict(private_vlan).get("associated"): - self.remove_command_from_config_list(vlan, "private-vlan association", commands) - - # Get the diff b/w want n have - - want_dict = dict_to_set(want, sort_dictionary=True) - have_dict = dict_to_set(have, sort_dictionary=True) - diff = want_dict - have_dict - have_diff = have_dict - want_dict - - if diff: - if have_diff and (self.state == "replaced" or self.state == "overridden"): - negate_have_config(diff, have_diff, vlan, commands) - - if not self.configuration: - name = dict(diff).get("name") - state = dict(diff).get("state") - shutdown = dict(diff).get("shutdown") - mtu = dict(diff).get("mtu") - remote_span = dict(diff).get("remote_span") - private_vlan = dict(diff).get("private_vlan") - - if name: - self.add_command_to_config_list(vlan, "name {0}".format(name), commands) - if state: - self.add_command_to_config_list(vlan, "state {0}".format(state), commands) - if mtu: - self.add_command_to_config_list(vlan, "mtu {0}".format(mtu), commands) - if remote_span: - self.add_command_to_config_list(vlan, "remote-span", commands) - - if private_vlan: - private_vlan_type = dict(private_vlan).get("type") - private_vlan_associated = dict(private_vlan).get("associated") - if private_vlan_type: - self.add_command_to_config_list( - vlan, - "private-vlan {0}".format(private_vlan_type), - commands, - ) - if private_vlan_associated: - associated_list = ",".join( - str(e) for e in private_vlan_associated - ) # Convert python list to string with elements separated by a comma - self.add_command_to_config_list( - vlan, - "private-vlan association {0}".format(associated_list), - commands, - ) - if shutdown == "enabled": - self.add_command_to_config_list(vlan, "shutdown", commands) - elif shutdown == "disabled": - self.add_command_to_config_list(vlan, "no shutdown", commands) + for k, want in iteritems(wantd): + self._compare(want=want, have=haved.pop(k, {}), resource=resource) + + def _compare(self, want, have, resource=None): + """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 Vlans network resource. + """ + begin = len(self.commands) + self.compare(parsers=self.parsers, want=want, have=have) + if want.get("shutdown") != have.get("shutdown"): + if want.get("shutdown"): + self.addcmd(want, "shutdown", True) else: - member_dict = dict(diff).get("member") - if member_dict: - member_dict = dict(member_dict) - member_vni = member_dict.get("vni") - member_evi = member_dict.get("evi") - commands.extend( - self._remove_vlan_vni_evi_mapping( - want, - ), - ) - commands.extend( - [ - vlan, - self._get_member_cmds(member_dict), - ], - ) - - elif have_diff and (self.state == "replaced" or self.state == "overridden"): - negate_have_config(diff, have_diff, vlan, commands) - return commands - - def _clear_config(self, want, have): - # Delete the interface config based on the want and have config - commands = [] - vlan = self.vlan_parent.format(have.get("vlan_id")) - - if ( - have.get("vlan_id") - and "default" not in have.get("name", "") - and (have.get("vlan_id") != want.get("vlan_id") or self.state == "deleted") - ): - self.remove_command_from_config_list(vlan, "vlan", commands) - if self.configuration and self.state == "overridden": - self.have_now.remove(have) - elif "default" not in have.get("name", ""): - if not self.configuration: - if have.get("mtu") != want.get("mtu"): - self.remove_command_from_config_list(vlan, "mtu", commands) - if have.get("remote_span") != want.get("remote_span") and want.get("remote_span"): - self.remove_command_from_config_list(vlan, "remote-span", commands) - if have.get("shutdown") != want.get("shutdown") and want.get("shutdown"): - self.remove_command_from_config_list(vlan, "shutdown", commands) - if have.get("state") != want.get("state") and want.get("state"): - self.remove_command_from_config_list(vlan, "state", commands) - return commands - - def _remove_vlan_vni_evi_mapping(self, want_dict): - commands = [] - have_copy = self.have_now.copy() - vlan = want_dict["vlan_id"] - for vlan_dict in have_copy: - if vlan_dict["vlan_id"] == vlan: - if "member" in vlan_dict: - commands.extend( - [ - self.vlan_parent.format(vlan), - self._get_member_cmds( - vlan_dict.get("member", {}), - prefix="no", - ), - ], - ) - vlan_dict.pop("member") - if vlan_dict["vlan_id"] != vlan: - delete_member = False - have_vni = vlan_dict.get("member", {}).get("vni") - have_evi = vlan_dict.get("member", {}).get("evi") - if have_vni and (have_vni == want_dict["member"].get("vni")): - delete_member = True - if have_evi and (have_evi == want_dict["member"].get("evi")): - delete_member = True - if delete_member: - commands.extend( - [ - self.vlan_parent.format(vlan_dict["vlan_id"]), - self._get_member_cmds( - vlan_dict.get("member", {}), - prefix="no", - ), - ], - ) - self.have_now.remove(vlan_dict) - return commands - - def _get_member_cmds(self, member_dict, prefix=""): - cmd = "" - if prefix: - prefix = prefix + " " - member_vni = member_dict.get("vni") - member_evi = member_dict.get("evi") - - if member_evi: - cmd = prefix + "member evpn-instance {0} vni {1}".format(member_evi, member_vni) - elif member_vni: - cmd = prefix + "member vni {0}".format(member_vni) + if want: + self.addcmd(want, "shutdown", False) + elif have.get("shutdown"): + # handles deleted as want be blank and only + # negates if no shutdown + self.addcmd(have, "shutdown", False) + if len(self.commands) != begin: + self.commands.insert( + begin, + self._tmplt.render(want or have, resource, False), + ) - return cmd + def purge(self, have, resource): + """Handle operation for purged state""" + if resource == "vlan_configuration": + self.commands.append(self._tmplt.render(have, resource, True)) diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/acls/acls.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/acls/acls.py index 2be369a7a..341491f2e 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/acls/acls.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/acls/acls.py @@ -105,7 +105,10 @@ class AclsFacts(object): if namedata: # parse just names to update empty acls - templateObjName = NetworkTemplate(lines=namedata.splitlines(), tmplt=AclsTemplate()) + templateObjName = NetworkTemplate( + lines=namedata.splitlines(), + tmplt=AclsTemplate(), + ) raw_acl_names = templateObjName.parse() raw_acls = self.populate_empty_acls(raw_acls, raw_acl_names) @@ -114,7 +117,10 @@ class AclsFacts(object): if raw_acls.get("acls"): for k, v in iteritems(raw_acls.get("acls")): - if v.get("afi") == "ipv4" and v.get("acl_type") in ["standard", "extended"]: + if v.get("afi") == "ipv4" and v.get("acl_type") in [ + "standard", + "extended", + ]: del v["afi"] temp_v4.append(v) elif v.get("afi") == "ipv6": @@ -142,10 +148,15 @@ class AclsFacts(object): def process_protocol_options(each): for each_ace in each.get("aces"): if each.get("acl_type") == "standard": - if len(each_ace.get("source", {})) == 1 and each_ace.get("source", {}).get( + if len(each_ace.get("source", {})) == 1 and each_ace.get( + "source", + {}, + ).get( "address", ): - each_ace["source"]["host"] = each_ace["source"].pop("address") + each_ace["source"]["host"] = each_ace["source"].pop( + "address", + ) if each_ace.get("source", {}).get("address"): addr = each_ace.get("source", {}).get("address") if addr[-1] == ",": @@ -159,7 +170,10 @@ class AclsFacts(object): if each_ace.get("icmp_igmp_tcp_protocol"): each_ace["protocol_options"] = { each_ace["protocol"]: { - each_ace.pop("icmp_igmp_tcp_protocol").replace("-", "_"): True, + each_ace.pop("icmp_igmp_tcp_protocol").replace( + "-", + "_", + ): True, }, } if each_ace.get("protocol_number"): @@ -172,13 +186,21 @@ class AclsFacts(object): ace_entry = [] ace_rem = [] rem = {} + # every remarks is one list item which has a sequence number + # every ace remark is preserved and ordered + # at the end of each sequence it is flushed to a ace entry for i in aces: + # i here denotes an ace, which would be populated with remarks entries if i.get("is_remark_for"): if not rem.get(i.get("is_remark_for")): rem[i.get("is_remark_for")] = {"remarks": []} - rem[i.get("is_remark_for")]["remarks"].append(i.get("the_remark")) + rem[i.get("is_remark_for")]["remarks"].append( + i.get("the_remark"), + ) else: - rem[i.get("is_remark_for")]["remarks"].append(i.get("the_remark")) + rem[i.get("is_remark_for")]["remarks"].append( + i.get("the_remark"), + ) else: if rem: if rem.get(i.get("sequence")): @@ -187,12 +209,27 @@ class AclsFacts(object): ace_entry.append(i) if rem: # pending remarks - pending_rem = rem.get("remark") - ace_entry.append({"remarks": pending_rem.get("remarks")}) + for pending_rem_seq, pending_rem_val in rem.items(): + # there can be ace entry with just a remarks and no ace actually + # 10 remarks I am a remarks + # 20 ..... so onn + if pending_rem_seq != "remark": + ace_entry.append( + { + "sequence": pending_rem_seq, + "remarks": pending_rem_val.get("remarks"), + }, + ) + else: + # this handles the classic set of remarks at the end, which is not tied to + # any sequence number + pending_rem = rem.get("remark", {}) + ace_entry.append({"remarks": pending_rem.get("remarks")}) return ace_entry for each in temp_v4: if each.get("aces"): + # handling remarks for each ace entry each["aces"] = collect_remarks(each.get("aces")) process_protocol_options(each) diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/bgp_global/bgp_global.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/bgp_global/bgp_global.py index 37bbfabfd..c5b90d9e7 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/bgp_global/bgp_global.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/bgp_global/bgp_global.py @@ -35,6 +35,17 @@ class Bgp_globalFacts(object): def get_bgp_global_data(self, connection): return connection.get("show running-config | section ^router bgp") + def _set_defaults(self, objs): + """makes data as per the facts after data obtained from parsers""" + + if objs.get("as_number"): + objs.setdefault("bgp", {}).setdefault("default", {}).setdefault("ipv4_unicast", True) + objs.setdefault("bgp", {}).setdefault("default", {}).setdefault( + "route_target", + {}, + ).setdefault("filter", True) + return objs + def populate_facts(self, connection, ansible_facts, data=None): """Populate the facts for Bgp_global network resource @@ -56,6 +67,9 @@ class Bgp_globalFacts(object): module=self._module, ) objs = bgp_global_parser.parse() + if objs: + objs = self._set_defaults(objs) + neighbor_list = objs.get("neighbors", {}) if neighbor_list: objs["neighbors"] = sorted( diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/facts.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/facts.py index 7718b474d..583c86b51 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/facts.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/facts.py @@ -104,7 +104,12 @@ from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.vxlan_ ) -FACT_LEGACY_SUBSETS = dict(default=Default, hardware=Hardware, interfaces=Interfaces, config=Config) +FACT_LEGACY_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config, +) FACT_RESOURCE_SUBSETS = dict( interfaces=InterfacesFacts, @@ -155,7 +160,11 @@ class Facts(FactsBase): :return: the facts gathered """ if self.VALID_RESOURCE_SUBSETS: - self.get_network_resources_facts(FACT_RESOURCE_SUBSETS, resource_facts_type, data) + self.get_network_resources_facts( + FACT_RESOURCE_SUBSETS, + resource_facts_type, + data, + ) if self.VALID_LEGACY_GATHER_SUBSETS: self.get_network_legacy_facts(FACT_LEGACY_SUBSETS, legacy_facts_type) diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/l3_interfaces/l3_interfaces.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/l3_interfaces/l3_interfaces.py index bea49159b..1b36db57a 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/l3_interfaces/l3_interfaces.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/l3_interfaces/l3_interfaces.py @@ -40,6 +40,14 @@ class L3_InterfacesFacts(object): def get_l3_interfaces_data(self, connection): return connection.get("show running-config | section ^interface") + def _set_defaults(self, objs): + """Set default parameters""" + + for intf in objs: + if intf.get("name") and intf["name"][:4] == "Vlan": + intf.setdefault("autostate", True) + return objs + def populate_facts(self, connection, ansible_facts, data=None): """Populate the facts for l3 interfaces :param connection: the device connection @@ -71,6 +79,10 @@ class L3_InterfacesFacts(object): temp = sorted(temp, key=lambda k, sk="name": k[sk]) objs = temp + + if objs: + objs = self._set_defaults(objs) + facts = {} if objs: facts["l3_interfaces"] = [] diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/vlans/vlans.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/vlans/vlans.py index 9b506fa90..7bfed68d5 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/vlans/vlans.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/vlans/vlans.py @@ -16,8 +16,6 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -import re - from copy import deepcopy from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils @@ -25,6 +23,9 @@ from ansible_collections.ansible.netcommon.plugins.module_utils.network.common i from ansible_collections.cisco.ios.plugins.module_utils.network.ios.argspec.vlans.vlans import ( VlansArgs, ) +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.rm_templates.vlans import ( + VlansTemplate, +) class VlansFacts(object): @@ -44,154 +45,60 @@ class VlansFacts(object): self.generated_spec = utils.generate_dict(facts_argument_spec) - def get_vlans_data(self, connection, configuration): + def get_vlans_data(self, connection): """Checks device is L2/L3 and returns facts gracefully. Does not fail module. """ - if configuration: - cmd = "show running-config | sec ^vlan configuration .+" - else: - cmd = "show vlan" check_os_type = connection.get_device_info() if check_os_type.get("network_os_type") == "L3": return "" - return connection.get(cmd) + return connection.get("show vlan") + + def get_vlan_conf_data(self, connection): + return connection.get("show running-config | section ^vlan configuration .+") + + def populate_vlans_config_facts(self, connection, data=None): + """Process config for Vlans Configurations - def populate_facts(self, connection, ansible_facts, data=None): - """Populate the facts for vlans :param connection: the device connection :param ansible_facts: Facts dictionary :param data: previously collected conf + :rtype: dictionary :returns: facts """ - configuration = self._module.params["configuration"] - objs = [] if not data: - data = self.get_vlans_data(connection, configuration) - if not configuration: - objs = self.parse_vlan(data) - else: - objs = self.parse_vlan_config(data) + data = self.get_vlan_conf_data(connection) - facts = {} - if objs: - facts["vlans"] = [] - params = utils.validate_config(self.argument_spec, {"config": objs}) - - for cfg in params["config"]: - facts["vlans"].append(utils.remove_empties(cfg)) - ansible_facts["ansible_network_resources"].update(facts) - return ansible_facts + # parse native config using the Vlan_configurations template + vlan_configurations_parser = VlansTemplate( + lines=data.splitlines(), + module=self._module, + ) + return vlan_configurations_parser.parse() - def render_config(self, spec, conf, vlan_info): - """ - 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 + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for vlans + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf :rtype: dictionary - :returns: The generated config + :returns: facts """ - config = deepcopy(spec) - - if vlan_info == "Name" and "VLAN Name" not in conf: - conf = list(filter(None, conf.split(" "))) - config["vlan_id"] = int(conf[0]) - config["name"] = conf[1] - state_idx = 2 - for i in range(2, len(conf)): # check for index where state starts - if conf[i] in ["suspended", "active"]: - state_idx = i - break - elif conf[i].split("/")[0] in ["sus", "act"]: - state_idx = i - break - config["name"] += " " + conf[i] - try: - if len(conf[state_idx].split("/")) > 1: - _state = conf[state_idx].split("/")[0] - if _state == "sus": - config["state"] = "suspend" - elif _state == "act": - config["state"] = "active" - config["shutdown"] = "enabled" - else: - if conf[state_idx] == "suspended": - config["state"] = "suspend" - elif conf[state_idx] == "active": - config["state"] = "active" - config["shutdown"] = "disabled" - except IndexError: - pass - elif vlan_info == "Type" and "VLAN Type" not in conf: - conf = list(filter(None, conf.split(" "))) - config["mtu"] = int(conf[3]) - elif vlan_info == "Remote": - if len(conf.split(",")) > 1 or conf.isdigit(): - remote_span_vlan = [] - if len(conf.split(",")) > 1: - remote_span_vlan = conf.split(",") - else: - remote_span_vlan.append(conf) - remote_span = [] - for each in remote_span_vlan: - split_sp_list = each.split("-") - if len(split_sp_list) > 1: # break range - for r_sp in range(int(split_sp_list[0]), int(split_sp_list[1]) + 1): - remote_span.append(r_sp) - else: - remote_span.append(int(each)) - config["remote_span"] = remote_span - - elif vlan_info == "Private" and "Primary Secondary" not in conf: - conf = list(filter(None, conf.split(" "))) - - pri_idx = 0 - sec_idx = 1 - priv_type_idx = 2 - - config["tmp_pvlans"] = { - "primary": conf[pri_idx], - "secondary": conf[sec_idx], - "sec_type": conf[priv_type_idx], - } - return utils.remove_empties(config) - - def parse_vlan_config(self, vlan_conf): - vlan_list = list() - - re1 = re.compile(r"^vlan configuration +(?P<vlan>\d+)$") - re2 = re.compile(r"^member +(evpn\-instance +(?P<evi>\d+) )?vni (?P<vni>[\d\-]+)$") - - for line in vlan_conf.splitlines(): - line = line.strip() - m = re1.match(line) - if m: - vlan = m.groupdict()["vlan"] - vlan_dict = {"vlan_id": vlan} - continue - - m = re2.match(line) - if m: - group = m.groupdict() - vlan_dict.update({"member": {}}) - vlan_dict["member"].update({"vni": group["vni"]}) - if group["evi"]: - vlan_dict["member"].update({"evi": group["evi"]}) - vlan_list.append(vlan_dict) - - return vlan_list - - def parse_vlan(self, data): objs = [] mtu_objs = [] remote_objs = [] final_objs = [] pvlan_objs = [] + conf_data = {} + + if not data: + data = self.get_vlans_data(connection) + + # deals with vlan configuration config only + conf_data = self.populate_vlans_config_facts(connection, data) # operate on a collection of resource x config = data.split("\n") @@ -199,6 +106,7 @@ class VlansFacts(object): vlan_info = "" temp = "" vlan_name = True + for conf in config: if len(list(filter(None, conf.split(" ")))) <= 2 and vlan_name: temp = temp + conf @@ -231,6 +139,7 @@ class VlansFacts(object): pvlan_objs.append(obj) elif obj: objs.append(obj) + # Appending MTU value to the retrieved dictionary for o, m in zip(objs, mtu_objs): o.update(m) @@ -250,7 +159,6 @@ class VlansFacts(object): pvlan_final = {} if len(pvlan_objs) > 0: # Sanitize and structure everything - for data in pvlan_objs: pvdata = data.get("tmp_pvlans") privlan = pvdata.get("primary") @@ -270,7 +178,9 @@ class VlansFacts(object): "private_vlan": {"type": "primary", "associated": []}, } if secvlan and (isinstance(secvlan, int) or secvlan.isnumeric()): - pvlan_final[privlan]["private_vlan"]["associated"].append(int(secvlan)) + pvlan_final[privlan]["private_vlan"]["associated"].append( + int(secvlan), + ) # Associate with the proper VLAN in final_objs for vlan_id, data in pvlan_final.items(): @@ -278,7 +188,103 @@ class VlansFacts(object): if vlan_id == every.get("vlan_id"): every.update(data) - if final_objs: - return objs - else: - return {} + facts = {} + + if conf_data: + for vlan in objs: + if conf_data.get(vlan.get("vlan_id")): + member_data = conf_data.pop(vlan.get("vlan_id")) + vlan.update(member_data) + + if conf_data: # if any vlan configuration data is pending add it to facts + for vlanid, conf in conf_data.items(): + objs.append(conf) + + if objs: + facts["vlans"] = [] + params = utils.validate_config(self.argument_spec, {"config": objs}) + + for cfg in params["config"]: + facts["vlans"].append(utils.remove_empties(cfg)) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts + + def render_config(self, spec, conf, vlan_info): + """ + Render config as dictionary structure and delete keys + from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + config = deepcopy(spec) + + if vlan_info == "Name" and "VLAN Name" not in conf: + conf = list(filter(None, conf.split(" "))) + config["vlan_id"] = int(conf[0]) + config["name"] = conf[1] + state_idx = 2 + for i in range(2, len(conf)): # check for index where state starts + if conf[i] in ["suspended", "active"]: + state_idx = i + break + elif conf[i].split("/")[0] in ["sus", "act"]: + state_idx = i + break + config["name"] += " " + conf[i] + try: + if len(conf[state_idx].split("/")) > 1: + _state = conf[state_idx].split("/")[0] + if _state == "sus": + config["state"] = "suspend" + elif _state == "act": + config["state"] = "active" + config["shutdown"] = "enabled" + else: + if conf[state_idx] == "suspended": + config["state"] = "suspend" + elif conf[state_idx] == "active": + config["state"] = "active" + config["shutdown"] = "disabled" + except IndexError: + pass + elif vlan_info == "Type" and "VLAN Type" not in conf: + conf = list(filter(None, conf.split(" "))) + config["mtu"] = int(conf[3]) + elif vlan_info == "Remote": + if len(conf.split(",")) > 1 or conf.isdigit(): + remote_span_vlan = [] + if len(conf.split(",")) > 1: + remote_span_vlan = conf.split(",") + else: + remote_span_vlan.append(conf) + remote_span = [] + for each in remote_span_vlan: + split_sp_list = each.split("-") + if len(split_sp_list) > 1: # break range + for r_sp in range( + int(split_sp_list[0]), + int(split_sp_list[1]) + 1, + ): + remote_span.append(r_sp) + else: + remote_span.append(int(each)) + config["remote_span"] = remote_span + + elif vlan_info == "Private" and "Primary Secondary" not in conf: + conf = list(filter(None, conf.split(" "))) + + pri_idx = 0 + sec_idx = 1 + priv_type_idx = 2 + + config["tmp_pvlans"] = { + "primary": conf[pri_idx], + "secondary": conf[sec_idx], + "sec_type": conf[priv_type_idx], + } + + return utils.remove_empties(config) diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/__init__.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/__init__.py deleted file mode 100644 index e69de29bb..000000000 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/__init__.py +++ /dev/null diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/config/__init__.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/config/__init__.py deleted file mode 100644 index e69de29bb..000000000 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/config/__init__.py +++ /dev/null diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/config/base.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/config/base.py deleted file mode 100644 index f1b9cdb90..000000000 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/config/base.py +++ /dev/null @@ -1,82 +0,0 @@ -# -# (c) 2019, Ansible by Red Hat, inc -# 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 -from ansible.module_utils.six import iteritems -from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( - NetworkConfig, -) -from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list - - -class ConfigBase(object): - argument_spec = {} - - mutually_exclusive = [] - - identifier = () - - def __init__(self, **kwargs): - self.values = {} - self._rendered_configuration = {} - self.active_configuration = None - - for item in self.identifier: - self.values[item] = kwargs.pop(item) - - for key, value in iteritems(kwargs): - if key in self.argument_spec: - setattr(self, key, value) - - for key, value in iteritems(self.argument_spec): - if value.get("default"): - if not getattr(self, key, None): - setattr(self, key, value.get("default")) - - def __getattr__(self, key): - if key in self.argument_spec: - return self.values.get(key) - - def __setattr__(self, key, value): - if key in self.argument_spec: - if key in self.identifier: - raise TypeError("cannot set value") - elif value is not None: - self.values[key] = value - else: - super(ConfigBase, self).__setattr__(key, value) - - def context_config(self, cmd): - if "context" not in self._rendered_configuration: - self._rendered_configuration["context"] = list() - self._rendered_configuration["context"].extend(to_list(cmd)) - - def global_config(self, cmd): - if "global" not in self._rendered_configuration: - self._rendered_configuration["global"] = list() - self._rendered_configuration["global"].extend(to_list(cmd)) - - def get_rendered_configuration(self): - config = list() - for section in ("context", "global"): - config.extend(self._rendered_configuration.get(section, [])) - return config - - def set_active_configuration(self, config): - self.active_configuration = config - - def render(self, config=None): - raise NotImplementedError - - def get_section(self, config, section): - if config is not None: - netcfg = NetworkConfig(indent=1, contents=config) - try: - config = netcfg.get_block_config(to_list(section)) - except ValueError: - config = None - return config diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/config/bgp/__init__.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/config/bgp/__init__.py deleted file mode 100644 index e69de29bb..000000000 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/config/bgp/__init__.py +++ /dev/null diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/config/bgp/address_family.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/config/bgp/address_family.py deleted file mode 100644 index e4c2bd803..000000000 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/config/bgp/address_family.py +++ /dev/null @@ -1,147 +0,0 @@ -# -# (c) 2019, Ansible by Red Hat, inc -# 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 re - -from ansible.module_utils.common.network import to_netmask -from ansible.module_utils.six import iteritems -from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list - -from ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.cli.config.bgp.neighbors import ( - AFNeighbors, -) -from ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers import ( - CliProvider, -) - - -class AddressFamily(CliProvider): - def render(self, config=None): - commands = list() - safe_list = list() - - router_context = "router bgp %s" % self.get_value("config.bgp_as") - context_config = None - - for item in self.get_value("config.address_family"): - context = "address-family %s" % item["afi"] - if item["safi"] != "unicast": - context += " %s" % item["safi"] - context_commands = list() - - if config: - context_path = [router_context, context] - context_config = self.get_config_context(config, context_path, indent=1) - - for key, value in iteritems(item): - if value is not None: - meth = getattr(self, "_render_%s" % key, None) - if meth: - resp = meth(item, context_config) - if resp: - context_commands.extend(to_list(resp)) - - if context_commands: - commands.append(context) - commands.extend(context_commands) - commands.append("exit-address-family") - - safe_list.append(context) - - if self.params["operation"] == "replace": - if config: - resp = self._negate_config(config, safe_list) - commands.extend(resp) - - return commands - - def _negate_config(self, config, safe_list=None): - commands = list() - matches = re.findall(r"(address-family .+)$", config, re.M) - for item in set(matches).difference(safe_list): - commands.append("no %s" % item) - return commands - - def _render_auto_summary(self, item, config=None): - cmd = "auto-summary" - if item["auto_summary"] is False: - cmd = "no %s" % cmd - if not config or cmd not in config: - return cmd - - def _render_synchronization(self, item, config=None): - cmd = "synchronization" - if item["synchronization"] is False: - cmd = "no %s" % cmd - if not config or cmd not in config: - return cmd - - def _render_networks(self, item, config=None): - commands = list() - safe_list = list() - - for entry in item["networks"]: - network = entry["prefix"] - cmd = "network %s" % network - if entry["masklen"]: - cmd += " mask %s" % to_netmask(entry["masklen"]) - network += " mask %s" % to_netmask(entry["masklen"]) - if entry["route_map"]: - cmd += " route-map %s" % entry["route_map"] - network += " route-map %s" % entry["route_map"] - - safe_list.append(network) - - if not config or cmd not in config: - commands.append(cmd) - - if self.params["operation"] == "replace": - if config: - matches = re.findall(r"network (.*)", config, re.M) - for entry in set(matches).difference(safe_list): - commands.append("no network %s" % entry) - - return commands - - def _render_redistribute(self, item, config=None): - commands = list() - safe_list = list() - - for entry in item["redistribute"]: - option = entry["protocol"] - - cmd = "redistribute %s" % entry["protocol"] - - if entry["id"] and entry["protocol"] in ("ospf", "ospfv3", "eigrp"): - cmd += " %s" % entry["id"] - option += " %s" % entry["id"] - - if entry["metric"]: - cmd += " metric %s" % entry["metric"] - - if entry["route_map"]: - cmd += " route-map %s" % entry["route_map"] - - if not config or cmd not in config: - commands.append(cmd) - - safe_list.append(option) - - if self.params["operation"] == "replace": - if config: - matches = re.findall(r"redistribute (\S+)(?:\s*)(\d*)", config, re.M) - for i in range(0, len(matches)): - matches[i] = " ".join(matches[i]).strip() - for entry in set(matches).difference(safe_list): - commands.append("no redistribute %s" % entry) - - return commands - - def _render_neighbors(self, item, config): - """generate bgp neighbor configuration""" - return AFNeighbors(self.params).render(config, nbr_list=item["neighbors"]) diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/config/bgp/neighbors.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/config/bgp/neighbors.py deleted file mode 100644 index 4ee337b00..000000000 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/config/bgp/neighbors.py +++ /dev/null @@ -1,203 +0,0 @@ -# -# (c) 2019, Ansible by Red Hat, inc -# 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 re - -from ansible.module_utils.six import iteritems -from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list - -from ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers import ( - CliProvider, -) - - -class Neighbors(CliProvider): - def render(self, config=None, nbr_list=None): - commands = list() - safe_list = list() - if not nbr_list: - nbr_list = self.get_value("config.neighbors") - - for item in nbr_list: - neighbor_commands = list() - context = "neighbor %s" % item["neighbor"] - cmd = "%s remote-as %s" % (context, item["remote_as"]) - - if not config or cmd not in config: - neighbor_commands.append(cmd) - - for key, value in iteritems(item): - if value is not None: - meth = getattr(self, "_render_%s" % key, None) - if meth: - resp = meth(item, config) - if resp: - neighbor_commands.extend(to_list(resp)) - - commands.extend(neighbor_commands) - safe_list.append(context) - - if self.params["operation"] == "replace": - if config and safe_list: - commands.extend(self._negate_config(config, safe_list)) - - return commands - - def _negate_config(self, config, safe_list=None): - commands = list() - matches = re.findall(r"(neighbor \S+)", config, re.M) - for item in set(matches).difference(safe_list): - commands.append("no %s" % item) - return commands - - def _render_local_as(self, item, config=None): - cmd = "neighbor %s local-as %s" % (item["neighbor"], item["local_as"]) - if not config or cmd not in config: - return cmd - - def _render_port(self, item, config=None): - cmd = "neighbor %s port %s" % (item["neighbor"], item["port"]) - if not config or cmd not in config: - return cmd - - def _render_description(self, item, config=None): - cmd = "neighbor %s description %s" % (item["neighbor"], item["description"]) - if not config or cmd not in config: - return cmd - - def _render_enabled(self, item, config=None): - cmd = "neighbor %s shutdown" % item["neighbor"] - if item["enabled"] is True: - if not config or cmd in config: - cmd = "no %s" % cmd - return cmd - elif not config or cmd not in config: - return cmd - - def _render_update_source(self, item, config=None): - cmd = "neighbor %s update-source %s" % (item["neighbor"], item["update_source"]) - if not config or cmd not in config: - return cmd - - def _render_password(self, item, config=None): - cmd = "neighbor %s password %s" % (item["neighbor"], item["password"]) - if not config or cmd not in config: - return cmd - - def _render_ebgp_multihop(self, item, config=None): - cmd = "neighbor %s ebgp-multihop %s" % (item["neighbor"], item["ebgp_multihop"]) - if not config or cmd not in config: - return cmd - - def _render_peer_group(self, item, config=None): - cmd = "neighbor %s peer-group %s" % (item["neighbor"], item["peer_group"]) - if not config or cmd not in config: - return cmd - - def _render_timers(self, item, config): - """generate bgp timer related configuration""" - keepalive = item["timers"]["keepalive"] - holdtime = item["timers"]["holdtime"] - min_neighbor_holdtime = item["timers"]["min_neighbor_holdtime"] - neighbor = item["neighbor"] - - if keepalive and holdtime: - cmd = "neighbor %s timers %s %s" % (neighbor, keepalive, holdtime) - if min_neighbor_holdtime: - cmd += " %s" % min_neighbor_holdtime - if not config or cmd not in config: - return cmd - - -class AFNeighbors(CliProvider): - def render(self, config=None, nbr_list=None): - commands = list() - if not nbr_list: - return - - for item in nbr_list: - neighbor_commands = list() - for key, value in iteritems(item): - if value is not None: - meth = getattr(self, "_render_%s" % key, None) - if meth: - resp = meth(item, config) - if resp: - neighbor_commands.extend(to_list(resp)) - - commands.extend(neighbor_commands) - - return commands - - def _render_advertisement_interval(self, item, config=None): - cmd = "neighbor %s advertisement-interval %s" % ( - item["neighbor"], - item["advertisement_interval"], - ) - if not config or cmd not in config: - return cmd - - def _render_route_reflector_client(self, item, config=None): - cmd = "neighbor %s route-reflector-client" % item["neighbor"] - if item["route_reflector_client"] is False: - if not config or cmd in config: - cmd = "no %s" % cmd - return cmd - elif not config or cmd not in config: - return cmd - - def _render_route_server_client(self, item, config=None): - cmd = "neighbor %s route-server-client" % item["neighbor"] - if item["route_server_client"] is False: - if not config or cmd in config: - cmd = "no %s" % cmd - return cmd - elif not config or cmd not in config: - return cmd - - def _render_remove_private_as(self, item, config=None): - cmd = "neighbor %s remove-private-as" % item["neighbor"] - if item["remove_private_as"] is False: - if not config or cmd in config: - cmd = "no %s" % cmd - return cmd - elif not config or cmd not in config: - return cmd - - def _render_next_hop_self(self, item, config=None): - cmd = "neighbor %s next-hop-self" % item["neighbor"] - if item["next_hop_self"] is False: - if not config or cmd in config: - cmd = "no %s" % cmd - return cmd - elif not config or cmd not in config: - return cmd - - def _render_activate(self, item, config=None): - cmd = "neighbor %s activate" % item["neighbor"] - if item["activate"] is False: - if not config or cmd in config: - cmd = "no %s" % cmd - return cmd - elif not config or cmd not in config: - return cmd - - def _render_maximum_prefix(self, item, config=None): - cmd = "neighbor %s maximum-prefix %s" % (item["neighbor"], item["maximum_prefix"]) - if not config or cmd not in config: - return cmd - - def _render_prefix_list_in(self, item, config=None): - cmd = "neighbor %s prefix-list %s in" % (item["neighbor"], item["prefix_list_in"]) - if not config or cmd not in config: - return cmd - - def _render_prefix_list_out(self, item, config=None): - cmd = "neighbor %s prefix-list %s out" % (item["neighbor"], item["prefix_list_out"]) - if not config or cmd not in config: - return cmd diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/config/bgp/process.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/config/bgp/process.py deleted file mode 100644 index 2b54daa35..000000000 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/cli/config/bgp/process.py +++ /dev/null @@ -1,163 +0,0 @@ -# -# (c) 2019, Ansible by Red Hat, inc -# 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 re - -from ansible.module_utils.common.network import to_netmask -from ansible.module_utils.six import iteritems -from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list - -from ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.cli.config.bgp.address_family import ( - AddressFamily, -) -from ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.cli.config.bgp.neighbors import ( - Neighbors, -) -from ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers import ( - CliProvider, - register_provider, -) - - -REDISTRIBUTE_PROTOCOLS = [ - "ospf", - "ospfv3", - "eigrp", - "isis", - "static", - "connected", - "odr", - "lisp", - "mobile", - "rip", -] - - -@register_provider("ios", "ios_bgp") -class Provider(CliProvider): - def render(self, config=None): - commands = list() - - existing_as = None - if config: - match = re.search(r"router bgp (\d+)", config, re.M) - if match: - existing_as = match.group(1) - - operation = self.params["operation"] - - context = None - if self.params["config"]: - context = "router bgp %s" % self.get_value("config.bgp_as") - - if operation == "delete": - if existing_as: - commands.append("no router bgp %s" % existing_as) - elif context: - commands.append("no %s" % context) - - else: - self._validate_input(config) - if operation == "replace": - if existing_as and int(existing_as) != self.get_value("config.bgp_as"): - commands.append("no router bgp %s" % existing_as) - config = None - - elif operation == "override": - if existing_as: - commands.append("no router bgp %s" % existing_as) - config = None - - context_commands = list() - - for key, value in iteritems(self.get_value("config")): - if value is not None: - meth = getattr(self, "_render_%s" % key, None) - if meth: - resp = meth(config) - if resp: - context_commands.extend(to_list(resp)) - - if context and context_commands: - commands.append(context) - commands.extend(context_commands) - commands.append("exit") - return commands - - def _render_router_id(self, config=None): - cmd = "bgp router-id %s" % self.get_value("config.router_id") - if not config or cmd not in config: - return cmd - - def _render_log_neighbor_changes(self, config=None): - cmd = "bgp log-neighbor-changes" - log_neighbor_changes = self.get_value("config.log_neighbor_changes") - if log_neighbor_changes is True: - if not config or cmd not in config: - return cmd - elif log_neighbor_changes is False: - if config and cmd in config: - return "no %s" % cmd - - def _render_networks(self, config=None): - commands = list() - safe_list = list() - - for entry in self.get_value("config.networks"): - network = entry["prefix"] - cmd = "network %s" % network - if entry["masklen"] and entry["masklen"] not in (24, 16, 8): - cmd += " mask %s" % to_netmask(entry["masklen"]) - network += " mask %s" % to_netmask(entry["masklen"]) - - if entry["route_map"]: - cmd += " route-map %s" % entry["route_map"] - network += " route-map %s" % entry["route_map"] - - safe_list.append(network) - - if not config or cmd not in config: - commands.append(cmd) - - if self.params["operation"] == "replace": - if config: - matches = re.findall(r"network (.*)", config, re.M) - for entry in set(matches).difference(safe_list): - commands.append("no network %s" % entry) - - return commands - - def _render_neighbors(self, config): - """generate bgp neighbor configuration""" - return Neighbors(self.params).render(config) - - def _render_address_family(self, config): - """generate address-family configuration""" - return AddressFamily(self.params).render(config) - - def _validate_input(self, config=None): - def device_has_AF(config): - return re.search(r"address-family (?:.*)", config) - - address_family = self.get_value("config.address_family") - root_networks = self.get_value("config.networks") - operation = self.params["operation"] - - if operation == "replace": - if address_family and root_networks: - for item in address_family: - if item["networks"]: - raise ValueError( - "operation is replace but provided both root level network(s) and network(s) under %s %s address family" - % (item["afi"], item["safi"]), - ) - - if root_networks and config and device_has_AF(config): - raise ValueError( - "operation is replace and device has one or more address family activated but root level network(s) provided", - ) diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/module.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/module.py deleted file mode 100644 index 4de464795..000000000 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/providers/module.py +++ /dev/null @@ -1,66 +0,0 @@ -# -# (c) 2019, Ansible by Red Hat, inc -# 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 -from ansible.module_utils._text import to_text -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.connection import Connection - -from ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers import providers - - -class NetworkModule(AnsibleModule): - fail_on_missing_provider = True - - def __init__(self, connection=None, *args, **kwargs): - super(NetworkModule, self).__init__(*args, **kwargs) - - if connection is None: - connection = Connection(self._socket_path) - - self.connection = connection - - @property - def provider(self): - if not hasattr(self, "_provider"): - capabilities = self.from_json(self.connection.get_capabilities()) - - network_os = capabilities["device_info"]["network_os"] - network_api = capabilities["network_api"] - - if network_api == "cliconf": - connection_type = "network_cli" - - cls = providers.get(network_os, self._name.split(".")[-1], connection_type) - - if not cls: - msg = "unable to find suitable provider for network os %s" % network_os - if self.fail_on_missing_provider: - self.fail_json(msg=msg) - else: - self.warn(msg) - - obj = cls(self.params, self.connection, self.check_mode) - - setattr(self, "_provider", obj) - - return getattr(self, "_provider") - - def get_facts(self, subset=None): - try: - self.provider.get_facts(subset) - except Exception as exc: - self.fail_json(msg=to_text(exc)) - - def edit_config(self, config_filter=None): - current_config = self.connection.get_config(flags=config_filter) - try: - commands = self.provider.edit_config(current_config) - changed = bool(commands) - return {"commands": commands, "changed": changed} - except Exception as exc: - self.fail_json(msg=to_text(exc)) diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/acls.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/acls.py index b3afd65f9..54d082c2e 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/acls.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/acls.py @@ -23,9 +23,9 @@ from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.r def remarks_with_sequence(remarks_data): - cmd = "remark " + cmd = "remark" if remarks_data.get("remarks"): - cmd += remarks_data.get("remarks") + cmd += " " + remarks_data.get("remarks") if remarks_data.get("sequence"): cmd = to_text(remarks_data.get("sequence")) + " " + cmd return cmd @@ -191,7 +191,7 @@ class AclsTemplate(NetworkTemplate): "name": "{{ acl_name }}", "aces": [ { - "the_remark": "{{ remarks }}", + "the_remark": "'{{ remarks }}'", "order": "{{ order }}", "is_remark_for": "{{ sequence }}", }, @@ -206,14 +206,14 @@ class AclsTemplate(NetworkTemplate): r"""(?P<order>^\d+)\s*remark\s(?P<remarks>.+)$""", re.VERBOSE, ), - "setval": "{{ sequence }} remark", + "setval": remarks_with_sequence, "result": { "acls": { "{{ acl_name|d() }}": { "name": "{{ acl_name }}", "aces": [ { - "the_remark": "{{ remarks }}", + "the_remark": "'{{ remarks }}'", "order": "{{ order }}", "is_remark_for": "remark", }, @@ -238,7 +238,7 @@ class AclsTemplate(NetworkTemplate): "aces": [ { "sequence": "{{ sequence }}", - "remarks": ["{{ remarks }}"], + "remarks": ["'{{ remarks }}'"], }, ], }, @@ -248,7 +248,7 @@ class AclsTemplate(NetworkTemplate): { "name": "aces_ipv4_standard", "getval": re.compile( - r"""(\s*(?P<sequence>\d+))? + r"""^\s*((?P<sequence>\d+))? (\s(?P<grant>deny|permit)) (\s+(?P<address>((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)))? (\s(?P<wildcard>((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)))? @@ -281,17 +281,17 @@ class AclsTemplate(NetworkTemplate): { "name": "aces", "getval": re.compile( - r"""(\s*(?P<sequence>\d+))? - (\s*sequence\s(?P<sequence_ipv6>\d+))? - (\s*(?P<grant>deny|permit)) + r"""^\s*((?P<sequence>\d+))? + (\ssequence\s(?P<sequence_ipv6>\d+))? + (\s(?P<grant>deny|permit)) (\sevaluate\s(?P<evaluate>\S+))? - (\s(?P<protocol_num>\d+))? + (\s(?P<protocol_num>\d+)\s)? (\s*(?P<protocol>ahp|eigrp|esp|gre|icmp|igmp|ipinip|ipv6|ip|nos|ospf|pcp|pim|sctp|tcp|ip|udp))? - ((\s(?P<source_any>any))| - (\sobject-group\s(?P<source_obj_grp>\S+))| - (\shost\s(?P<source_host>\S+))| - (\s(?P<ipv6_source_address>\S+/\d+))| - (\s(?P<source_address>(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})\s\S+)))? + ((\s*(?P<source_any>any))| + (\s*object-group\s(?P<source_obj_grp>\S+))| + (\s*host\s(?P<source_host>\S+))| + (\s*(?P<ipv6_source_address>\S+/\d+))| + (\s*(?P<source_address>(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})\s\S+)))? (\seq\s(?P<seq>(\S+|\d+)))? (\sgt\s(?P<sgt>(\S+|\d+)))? (\slt\s(?P<slt>(\S+|\d+)))? diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/bgp_address_family.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/bgp_address_family.py index ca85440b2..eb5a08a37 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/bgp_address_family.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/bgp_address_family.py @@ -28,7 +28,11 @@ UNIQUE_NEIB_ADD = "{{ neighbor_address }}" class Bgp_address_familyTemplate(NetworkTemplate): def __init__(self, lines=None, module=None): - super(Bgp_address_familyTemplate, self).__init__(lines=lines, tmplt=self, module=module) + super(Bgp_address_familyTemplate, self).__init__( + lines=lines, + tmplt=self, + module=module, + ) PARSERS = [ { @@ -61,7 +65,11 @@ class Bgp_address_familyTemplate(NetworkTemplate): "{{ (' vrf ' + vrf) if vrf is defined else '' }}", "result": { "address_family": { - UNIQUE_AFI: {"afi": "{{ afi }}", "safi": "{{ safi }}", "vrf": "{{ vrf }}"}, + UNIQUE_AFI: { + "afi": "{{ afi }}", + "safi": "{{ safi }}", + "vrf": "{{ vrf }}", + }, }, }, "shared": True, @@ -136,7 +144,10 @@ class Bgp_address_familyTemplate(NetworkTemplate): "result": { "address_family": { UNIQUE_AFI: { - "table_map": {"name": "{{ name }}", "filter": "{{ not not filter }}"}, + "table_map": { + "name": "{{ name }}", + "filter": "{{ not not filter }}", + }, }, }, }, @@ -149,7 +160,10 @@ class Bgp_address_familyTemplate(NetworkTemplate): }, { "name": "default_information", - "getval": re.compile(r"""\s\sdefault-information\soriginate$""", re.VERBOSE), + "getval": re.compile( + r"""\s\sdefault-information\soriginate$""", + re.VERBOSE, + ), "setval": "default-information originate", "result": {"address_family": {UNIQUE_AFI: {"default_information": True}}}, }, @@ -162,7 +176,11 @@ class Bgp_address_familyTemplate(NetworkTemplate): re.VERBOSE, ), "setval": "default-metric {{ default_metric|string }}", - "result": {"address_family": {UNIQUE_AFI: {"default_metric": "{{ default_metric }}"}}}, + "result": { + "address_family": { + UNIQUE_AFI: {"default_metric": "{{ default_metric }}"}, + }, + }, }, { "name": "distance", @@ -234,7 +252,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): ), "setval": "bgp additional-paths select install", "result": { - "address_family": {UNIQUE_AFI: {"bgp": {"additional_paths": {"install": True}}}}, + "address_family": { + UNIQUE_AFI: {"bgp": {"additional_paths": {"install": True}}}, + }, }, }, { @@ -246,7 +266,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): ), "setval": "bgp additional-paths select receive", "result": { - "address_family": {UNIQUE_AFI: {"bgp": {"additional_paths": {"receive": True}}}}, + "address_family": { + UNIQUE_AFI: {"bgp": {"additional_paths": {"receive": True}}}, + }, }, }, { @@ -258,7 +280,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): ), "setval": "bgp additional-paths select send", "result": { - "address_family": {UNIQUE_AFI: {"bgp": {"additional_paths": {"send": True}}}}, + "address_family": { + UNIQUE_AFI: {"bgp": {"additional_paths": {"send": True}}}, + }, }, }, { @@ -316,7 +340,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "setval": "bgp nexthop trigger delay {{ bgp.nexthop.trigger.delay|string }}", "result": { "address_family": { - UNIQUE_AFI: {"bgp": {"nexthop": {"trigger": {"delay": "{{ delay }}"}}}}, + UNIQUE_AFI: { + "bgp": {"nexthop": {"trigger": {"delay": "{{ delay }}"}}}, + }, }, }, }, @@ -330,7 +356,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): ), "setval": "bgp nexthop trigger delay enable", "result": { - "address_family": {UNIQUE_AFI: {"bgp": {"nexthop": {"trigger": {"enable": True}}}}}, + "address_family": { + UNIQUE_AFI: {"bgp": {"nexthop": {"trigger": {"enable": True}}}}, + }, }, }, { @@ -342,7 +370,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): re.VERBOSE, ), "setval": "bgp redistribute-internal", - "result": {"address_family": {UNIQUE_AFI: {"bgp": {"redistribute_internal": True}}}}, + "result": { + "address_family": {UNIQUE_AFI: {"bgp": {"redistribute_internal": True}}}, + }, }, { "name": "bgp.route_map", @@ -365,7 +395,11 @@ class Bgp_address_familyTemplate(NetworkTemplate): re.VERBOSE, ), "setval": "bgp scan-time {{ bgp.scan_time }}", - "result": {"address_family": {UNIQUE_AFI: {"bgp": {"scan_time": "{{ scan_time }}"}}}}, + "result": { + "address_family": { + UNIQUE_AFI: {"bgp": {"scan_time": "{{ scan_time }}"}}, + }, + }, }, { "name": "bgp.soft_reconfig_backup", @@ -376,7 +410,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): re.VERBOSE, ), "setval": "bgp soft-reconfig-backup", - "result": {"address_family": {UNIQUE_AFI: {"bgp": {"soft_reconfig_backup": True}}}}, + "result": { + "address_family": {UNIQUE_AFI: {"bgp": {"soft_reconfig_backup": True}}}, + }, }, { "name": "bgp.update_group", @@ -429,7 +465,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "setval": "bgp slow-peer detection", "result": { "address_family": { - UNIQUE_AFI: {"bgp": {"slow_peer_options": {"detection": {"enable": True}}}}, + UNIQUE_AFI: { + "bgp": {"slow_peer_options": {"detection": {"enable": True}}}, + }, }, }, }, @@ -447,7 +485,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "address_family": { UNIQUE_AFI: { "bgp": { - "slow_peer_options": {"detection": {"threshold": "{{ threshold }}"}}, + "slow_peer_options": { + "detection": {"threshold": "{{ threshold }}"}, + }, }, }, }, @@ -464,7 +504,11 @@ class Bgp_address_familyTemplate(NetworkTemplate): "result": { "address_family": { UNIQUE_AFI: { - "bgp": {"slow_peer_options": {"split_update_group": {"dynamic": True}}}, + "bgp": { + "slow_peer_options": { + "split_update_group": {"dynamic": True}, + }, + }, }, }, }, @@ -480,7 +524,11 @@ class Bgp_address_familyTemplate(NetworkTemplate): "result": { "address_family": { UNIQUE_AFI: { - "bgp": {"slow_peer_options": {"split_update_group": {"permanent": True}}}, + "bgp": { + "slow_peer_options": { + "split_update_group": {"permanent": True}, + }, + }, }, }, }, @@ -543,7 +591,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "{{ (' ' + remote_as|string) if remote_as is defined else '' }}", "result": { "address_family": { - UNIQUE_AFI: {"neighbors": {UNIQUE_NEIB_ADD: {"remote_as": "{{ number }}"}}}, + UNIQUE_AFI: { + "neighbors": {UNIQUE_NEIB_ADD: {"remote_as": "{{ number }}"}}, + }, }, }, }, @@ -692,7 +742,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "result": { "address_family": { UNIQUE_AFI: { - "neighbors": {UNIQUE_NEIB_ADD: {"advertises": {"best-external": True}}}, + "neighbors": { + UNIQUE_NEIB_ADD: {"advertises": {"best-external": True}}, + }, }, }, }, @@ -792,7 +844,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "setval": "{{ ('neighbor ' + neighbor_address + ' aigp') if aigp.enable|d(False) else '' }}", "result": { "address_family": { - UNIQUE_AFI: {"neighbors": {UNIQUE_NEIB_ADD: {"aigp": {"enable": True}}}}, + UNIQUE_AFI: { + "neighbors": {UNIQUE_NEIB_ADD: {"aigp": {"enable": True}}}, + }, }, }, }, @@ -848,7 +902,11 @@ class Bgp_address_familyTemplate(NetworkTemplate): "{{ (' aigp send med') if aigp.send.med|d(False) else '' }}", "result": { "address_family": { - UNIQUE_AFI: {"neighbors": {UNIQUE_NEIB_ADD: {"aigp": {"send": {"med": True}}}}}, + UNIQUE_AFI: { + "neighbors": { + UNIQUE_NEIB_ADD: {"aigp": {"send": {"med": True}}}, + }, + }, }, }, }, @@ -863,7 +921,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "setval": "{{ ('neighbor ' + neighbor_address + ' allow-policy') if allow_policy|d(False) else '' }}", "result": { "address_family": { - UNIQUE_AFI: {"neighbors": {UNIQUE_NEIB_ADD: {"allow_policy": True}}}, + UNIQUE_AFI: { + "neighbors": {UNIQUE_NEIB_ADD: {"allow_policy": True}}, + }, }, }, }, @@ -881,7 +941,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "result": { "address_family": { UNIQUE_AFI: { - "neighbors": {UNIQUE_NEIB_ADD: {"allowas_in": "{{ allowas_in }}"}}, + "neighbors": { + UNIQUE_NEIB_ADD: {"allowas_in": "{{ allowas_in }}"}, + }, }, }, }, @@ -973,7 +1035,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "result": { "address_family": { UNIQUE_AFI: { - "neighbors": {UNIQUE_NEIB_ADD: {"cluster_id": "{{ cluster_id }}"}}, + "neighbors": { + UNIQUE_NEIB_ADD: {"cluster_id": "{{ cluster_id }}"}, + }, }, }, }, @@ -989,7 +1053,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "result": { "address_family": { UNIQUE_AFI: { - "neighbors": {UNIQUE_NEIB_ADD: {"default_originate": {"set": True}}}, + "neighbors": { + UNIQUE_NEIB_ADD: {"default_originate": {"set": True}}, + }, }, }, }, @@ -1031,7 +1097,7 @@ class Bgp_address_familyTemplate(NetworkTemplate): "neighbors": { UNIQUE_NEIB_ADD: { "neighbor_address": UNIQUE_NEIB_ADD, - "description": "{{ description }}", + "description": "'{{ description }}'", }, }, }, @@ -1260,7 +1326,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "{{ (' ' + inherit) if inherit is defined else '' }}", "result": { "address_family": { - UNIQUE_AFI: {"neighbors": {UNIQUE_NEIB_ADD: {"inherit": "{{ inherit }}"}}}, + UNIQUE_AFI: { + "neighbors": {UNIQUE_NEIB_ADD: {"inherit": "{{ inherit }}"}}, + }, }, }, }, @@ -1276,7 +1344,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "setval": "neighbor {{ neighbor_address }} internal-vpn-client", "result": { "address_family": { - UNIQUE_AFI: {"neighbors": {UNIQUE_NEIB_ADD: {"internal_vpn_client": True}}}, + UNIQUE_AFI: { + "neighbors": {UNIQUE_NEIB_ADD: {"internal_vpn_client": True}}, + }, }, }, }, @@ -1354,7 +1424,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "setval": "{{ ('neighbor ' + neighbor_address + ' next-hop-self') if nexthop_self.set|d(False) else '' }}", "result": { "address_family": { - UNIQUE_AFI: {"neighbors": {UNIQUE_NEIB_ADD: {"nexthop_self": {"set": True}}}}, + UNIQUE_AFI: { + "neighbors": {UNIQUE_NEIB_ADD: {"nexthop_self": {"set": True}}}, + }, }, }, }, @@ -1369,7 +1441,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "setval": "{{ ('neighbor ' + neighbor_address + ' next-hop-self all') if nexthop_self.all|d(False) else '' }}", "result": { "address_family": { - UNIQUE_AFI: {"neighbors": {UNIQUE_NEIB_ADD: {"nexthop_self": {"all": True}}}}, + UNIQUE_AFI: { + "neighbors": {UNIQUE_NEIB_ADD: {"nexthop_self": {"all": True}}}, + }, }, }, }, @@ -1385,7 +1459,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "result": { "address_family": { UNIQUE_AFI: { - "neighbors": {UNIQUE_NEIB_ADD: {"next_hop_unchanged": {"set": True}}}, + "neighbors": { + UNIQUE_NEIB_ADD: {"next_hop_unchanged": {"set": True}}, + }, }, }, }, @@ -1402,7 +1478,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "result": { "address_family": { UNIQUE_AFI: { - "neighbors": {UNIQUE_NEIB_ADD: {"next_hop_unchanged": {"allpaths": True}}}, + "neighbors": { + UNIQUE_NEIB_ADD: {"next_hop_unchanged": {"allpaths": True}}, + }, }, }, }, @@ -1460,7 +1538,10 @@ class Bgp_address_familyTemplate(NetworkTemplate): "path_attribute": { "discard": { "type": "{{ type }}", - "range": {"start": "{{ start }}", "end": "{{ end }}"}, + "range": { + "start": "{{ start }}", + "end": "{{ end }}", + }, "in": "{{ not not in }}", }, }, @@ -1493,7 +1574,10 @@ class Bgp_address_familyTemplate(NetworkTemplate): "path_attribute": { "treat_as_withdraw": { "type": "{{ type }}", - "range": {"start": "{{ start }}", "end": "{{ end }}"}, + "range": { + "start": "{{ start }}", + "end": "{{ end }}", + }, "in": "{{ not not in }}", }, }, @@ -1581,7 +1665,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "result": { "address_family": { UNIQUE_AFI: { - "neighbors": {UNIQUE_NEIB_ADD: {"remove_private_as": {"set": True}}}, + "neighbors": { + UNIQUE_NEIB_ADD: {"remove_private_as": {"set": True}}, + }, }, }, }, @@ -1598,7 +1684,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "result": { "address_family": { UNIQUE_AFI: { - "neighbors": {UNIQUE_NEIB_ADD: {"remove_private_as": {"all": True}}}, + "neighbors": { + UNIQUE_NEIB_ADD: {"remove_private_as": {"all": True}}, + }, }, }, }, @@ -1615,7 +1703,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "result": { "address_family": { UNIQUE_AFI: { - "neighbors": {UNIQUE_NEIB_ADD: {"remove_private_as": {"replace_as": True}}}, + "neighbors": { + UNIQUE_NEIB_ADD: {"remove_private_as": {"replace_as": True}}, + }, }, }, }, @@ -1675,7 +1765,11 @@ class Bgp_address_familyTemplate(NetworkTemplate): "setval": "{{ ('neighbor ' + neighbor_address + ' send-community') if send_community.set|d(False) else '' }}", "result": { "address_family": { - UNIQUE_AFI: {"neighbors": {UNIQUE_NEIB_ADD: {"send_community": {"set": True}}}}, + UNIQUE_AFI: { + "neighbors": { + UNIQUE_NEIB_ADD: {"send_community": {"set": True}}, + }, + }, }, }, }, @@ -1692,7 +1786,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "result": { "address_family": { UNIQUE_AFI: { - "neighbors": {UNIQUE_NEIB_ADD: {"send_community": {"both": True}}}, + "neighbors": { + UNIQUE_NEIB_ADD: {"send_community": {"both": True}}, + }, }, }, }, @@ -1710,7 +1806,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "result": { "address_family": { UNIQUE_AFI: { - "neighbors": {UNIQUE_NEIB_ADD: {"send_community": {"extended": True}}}, + "neighbors": { + UNIQUE_NEIB_ADD: {"send_community": {"extended": True}}, + }, }, }, }, @@ -1728,7 +1826,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "result": { "address_family": { UNIQUE_AFI: { - "neighbors": {UNIQUE_NEIB_ADD: {"send_community": {"standard": True}}}, + "neighbors": { + UNIQUE_NEIB_ADD: {"send_community": {"standard": True}}, + }, }, }, }, @@ -1840,7 +1940,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "{{ (' soft-reconfiguration inbound') if soft_reconfiguration|d(False) else '' }}", "result": { "address_family": { - UNIQUE_AFI: {"neighbors": {UNIQUE_NEIB_ADD: {"soft_reconfiguration": True}}}, + UNIQUE_AFI: { + "neighbors": {UNIQUE_NEIB_ADD: {"soft_reconfiguration": True}}, + }, }, }, }, @@ -1986,7 +2088,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "result": { "address_family": { UNIQUE_AFI: { - "neighbors": {UNIQUE_NEIB_ADD: {"ttl_security": "{{ ttl_security }}"}}, + "neighbors": { + UNIQUE_NEIB_ADD: {"ttl_security": "{{ ttl_security }}"}, + }, }, }, }, @@ -2005,7 +2109,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "result": { "address_family": { UNIQUE_AFI: { - "neighbors": {UNIQUE_NEIB_ADD: {"unsuppress_map": "{{ unsuppress_map }}"}}, + "neighbors": { + UNIQUE_NEIB_ADD: {"unsuppress_map": "{{ unsuppress_map }}"}, + }, }, }, }, @@ -2047,7 +2153,9 @@ class Bgp_address_familyTemplate(NetworkTemplate): "{{ (' ' + weight|string) if weight is defined else '' }}", "result": { "address_family": { - UNIQUE_AFI: {"neighbors": {UNIQUE_NEIB_ADD: {"weight": "{{ weight }}"}}}, + UNIQUE_AFI: { + "neighbors": {UNIQUE_NEIB_ADD: {"weight": "{{ weight }}"}}, + }, }, }, }, @@ -2130,7 +2238,10 @@ class Bgp_address_familyTemplate(NetworkTemplate): "name": "{{ context }}", "user": { "name": "{{ user }}", - "access": {"acl": "{{ acl }}", "ipv6": "{{ aclv6 }}"}, + "access": { + "acl": "{{ acl }}", + "ipv6": "{{ aclv6 }}", + }, "auth": {"md5": "{{ md5 }}", "sha": "{{ sha }}"}, "priv": { "des56": "{{ des56 }}", @@ -2663,7 +2774,12 @@ class Bgp_address_familyTemplate(NetworkTemplate): "address_family": { UNIQUE_AFI: { "redistribute": [ - {"vrf": {"name": "{{ name }}", "global": "{{ not not global }}"}}, + { + "vrf": { + "name": "{{ name }}", + "global": "{{ not not global }}", + }, + }, ], }, }, diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/bgp_global.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/bgp_global.py index 39fd548bc..2ce959fcd 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/bgp_global.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/bgp_global.py @@ -447,7 +447,7 @@ class Bgp_globalTemplate(NetworkTemplate): re.VERBOSE, ), "setval": "description {{ route_server_context.description }}", - "result": {"route_server_context": {"description": "{{ description }}"}}, + "result": {"route_server_context": {"description": "'{{ description }}'"}}, }, { "name": "synchronization", @@ -476,6 +476,36 @@ class Bgp_globalTemplate(NetworkTemplate): }, }, { + "name": "template.peer_policy", + "getval": re.compile( + r""" + \stemplate\speer-policy + (\s(?P<peer_policy>\S+)) + $""", + re.VERBOSE, + ), + "setval": "template peer-policy" + "{{ (' ' + template.peer_policy) if template.peer_policy is defined else '' }}", + "result": { + "template": {"peer_policy": "{{ peer_policy }}"}, + }, + }, + { + "name": "template.peer_session", + "getval": re.compile( + r""" + \stemplate\speer-session + (\s(?P<peer_session>\S+)) + $""", + re.VERBOSE, + ), + "setval": "template peer-session" + "{{ (' ' + template.peer_session) if template.peer_session is defined else '' }}", + "result": { + "template": {"peer_session": "{{ peer_session }}"}, + }, + }, + { "name": "timers", "getval": re.compile( r""" @@ -745,6 +775,21 @@ class Bgp_globalTemplate(NetworkTemplate): }, }, { + "name": "bgp.default.ipv4_unicast", + "getval": re.compile(r"""\sno\sbgp\sdefault\sipv4\-unicast""", re.VERBOSE), + "setval": "bgp default ipv4-unicast", + "result": {"bgp": {"default": {"ipv4_unicast": False}}}, + }, + { + "name": "bgp.default.route_target.filter", + "getval": re.compile( + r"""\sno\sbgp\sdefault\sroute\-target\sfilter""", + re.VERBOSE, + ), + "setval": "bgp default route-target filter", + "result": {"bgp": {"default": {"route_target": {"filter": False}}}}, + }, + { "name": "bgp.deterministic_med", "getval": re.compile(r"""\s(bgp\sdeterministic-med)""", re.VERBOSE), "setval": "bgp deterministic-med", @@ -1064,7 +1109,9 @@ class Bgp_globalTemplate(NetworkTemplate): "setval": "bgp nopeerup-delay nsf-switchover {{ bgp.nopeerup_delay_options.nsf_switchover|string }}", "result": { "bgp": { - "nopeerup_delay_options": {"nsf_switchover": "{{ nsf_switchover }}"}, + "nopeerup_delay_options": { + "nsf_switchover": "{{ nsf_switchover }}", + }, }, }, }, @@ -1080,7 +1127,9 @@ class Bgp_globalTemplate(NetworkTemplate): "setval": "bgp nopeerup-delay user-initiated {{ bgp.nopeerup_delay_options.user_initiated|string }}", "result": { "bgp": { - "nopeerup_delay_options": {"user_initiated": "{{ user_initiated }}"}, + "nopeerup_delay_options": { + "user_initiated": "{{ user_initiated }}", + }, }, }, }, @@ -1433,7 +1482,7 @@ class Bgp_globalTemplate(NetworkTemplate): "neighbors": { "{{ neighbor_address }}": { "neighbor_address": "{{ neighbor_address }}", - "description": "{{ description }}", + "description": "'{{ description }}'", }, }, }, diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/interfaces.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/interfaces.py index 084945143..6cd1eb84c 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/interfaces.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/interfaces.py @@ -53,7 +53,7 @@ class InterfacesTemplate(NetworkTemplate): "setval": "description {{ description }}", "result": { '{{ name }}': { - 'description': '{{ description }}', + 'description': "'{{ description }}'", }, }, }, diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/l3_interfaces.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/l3_interfaces.py index d57a8ded8..bd337befa 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/l3_interfaces.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/l3_interfaces.py @@ -66,6 +66,23 @@ class L3_interfacesTemplate(NetworkTemplate): # fmt: off PARSERS = [ { + "name": "autostate", + "getval": re.compile(r"""\s+no\s+autostate$""", re.VERBOSE), + "setval": "autostate", + "result": {"{{ name }}": {"autostate": False}}, + }, + { + "name": "mac_address", + "getval": re.compile( + r"""^mac-address + (\s(?P<mac_address>\S+)) + $""", + re.VERBOSE, + ), + "setval": "mac-address {{ mac_address }}", + "result": {"{{ name }}": {"mac_address": "{{ mac_address }}"}}, + }, + { "name": "name", "getval": re.compile( r"""^interface @@ -146,6 +163,33 @@ class L3_interfacesTemplate(NetworkTemplate): }, }, { + "name": "ipv4.source_interface", + "getval": re.compile( + r"""\s+ip\sunnumbered + (\s(?P<name>\S+)) + (\s(?P<poll>poll))? + (\s(?P<point_to_point>point-to-point))? + $""", + re.VERBOSE, + ), + "setval": "ip unnumbered {{ ipv4.source_interface.name }}" + "{{ ' poll' if ipv4.source_interface.poll|d(False) else ''}}" + "{{ ' point-to-point' if ipv4.source_interface.point_to_point|d(False) else ''}}", + "result": { + "{{ name }}": { + "ipv4": [ + { + "source_interface": { + "name": "{{ True if name is defined }}", + "poll": "{{ True if poll is defined }}", + "point_to_point": "{{ True if point_to_point is defined }}", + }, + }, + ], + }, + }, + }, + { "name": "ipv6.address", "getval": re.compile( r"""\s+ipv6\saddress @@ -227,5 +271,17 @@ class L3_interfacesTemplate(NetworkTemplate): }, }, }, + { + "name": "ipv6.enable", + "getval": re.compile(r"""\s+ipv6\s+enable$""", re.VERBOSE), + "setval": "ipv6 enable", + "result": { + "{{ name }}": { + "ipv6": [ + {"enable": True}, + ], + }, + }, + }, ] # fmt: on diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/ospfv2.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/ospfv2.py index 74b380cc5..80548254e 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/ospfv2.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/ospfv2.py @@ -721,9 +721,9 @@ class Ospfv2Template(NetworkTemplate): $""", re.VERBOSE, ), - "setval": "distance {{ admin_distance.distance }} " - "{{ ( admin_distance.address + ' ' + admin_distance.wildcard_bits ) if admin_distance.address is defined else '' }}" - "{{ ' ' + admin_distance.acl if admin_distance.acl is defined else '' }}", + "setval": "distance {{ distance.admin_distance.distance }} " + "{{ ( distance.admin_distance.address + ' ' + distance.admin_distance.wildcard_bits ) if distance.admin_distance.address is defined else '' }}" + "{{ ' ' + distance.admin_distance.acl if distance.admin_distance.acl is defined else '' }}", "result": { "processes": { "{{ pid }}": { diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/prefix_lists.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/prefix_lists.py index 43d163c5d..f19e58e27 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/prefix_lists.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/prefix_lists.py @@ -80,7 +80,7 @@ class Prefix_listsTemplate(NetworkTemplate): "{{ afi + name }}": { "name": "{{ name }}", "afi": "{{ 'ipv4' if afi is defined and afi=='ip' else 'ipv6' }}", - "description": "{{ description }}", + "description": "'{{ description }}'", }, }, }, diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/route_maps.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/route_maps.py index 1e569ccca..580987069 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/route_maps.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/route_maps.py @@ -39,11 +39,15 @@ def _tmplt_route_map_match(config_data): cmd += " best {best}".format(**config_data["match"]["additional_paths"]) if config_data["match"]["additional_paths"].get("best_range"): cmd += " best-range" - if config_data["match"]["additional_paths"]["best_range"].get("lower_limit"): + if config_data["match"]["additional_paths"]["best_range"].get( + "lower_limit", + ): cmd += " lower-limit {lower_limit}".format( **config_data["match"]["additional_paths"]["best_range"], ) - if config_data["match"]["additional_paths"]["best_range"].get("upper_limit"): + if config_data["match"]["additional_paths"]["best_range"].get( + "upper_limit", + ): cmd += " upper-limit {upper_limit}".format( **config_data["match"]["additional_paths"]["best_range"], ) @@ -231,7 +235,10 @@ def _tmplt_route_map_match_ip(config_data): config_data["match"]["ip"]["address"]["prefix_lists"], ) elif config_data["match"]["ip"]["address"].get("acls"): - cmd = construct_cmd_from_list(cmd, config_data["match"]["ip"]["address"]["acls"]) + cmd = construct_cmd_from_list( + cmd, + config_data["match"]["ip"]["address"]["acls"], + ) if config_data["match"]["ip"].get("flowspec"): cmd += " flowspec" if config_data["match"]["ip"]["flowspec"].get("dest_pfx"): @@ -245,7 +252,10 @@ def _tmplt_route_map_match_ip(config_data): config_data["match"]["ip"]["flowspec"]["prefix_lists"], ) elif config_data["match"]["ip"]["flowspec"].get("acls"): - cmd = construct_cmd_from_list(cmd, config_data["match"]["ip"]["flowspec"]["acls"]) + cmd = construct_cmd_from_list( + cmd, + config_data["match"]["ip"]["flowspec"]["acls"], + ) if config_data["match"]["ip"].get("next_hop"): cmd += " next-hop" if config_data["match"]["ip"]["next_hop"].get("prefix_lists"): @@ -255,7 +265,10 @@ def _tmplt_route_map_match_ip(config_data): config_data["match"]["ip"]["next_hop"]["prefix_lists"], ) elif config_data["match"]["ip"]["next_hop"].get("acls"): - cmd = construct_cmd_from_list(cmd, config_data["match"]["ip"]["next_hop"]["acls"]) + cmd = construct_cmd_from_list( + cmd, + config_data["match"]["ip"]["next_hop"]["acls"], + ) if config_data["match"]["ip"].get("redistribution_source"): cmd += " redistribution-source" if config_data["match"]["ip"]["redistribution_source"].get("prefix_lists"): @@ -411,7 +424,9 @@ def _tmplt_route_map_set(config_data): cmd += " additive" command.append(cmd) if set["extcommunity"].get("soo"): - command.append("set extcommunity soo {soo}".format(**set["extcommunity"])) + command.append( + "set extcommunity soo {soo}".format(**set["extcommunity"]), + ) if set["extcommunity"].get("vpn_distinguisher"): cmd = "set extcommunity vpn-distinguisher" if set["extcommunity"]["vpn_distinguisher"].get("range"): @@ -419,7 +434,9 @@ def _tmplt_route_map_set(config_data): **set["extcommunity"]["vpn_distinguisher"]["range"], ) elif set["extcommunity"]["vpn_distinguisher"].get("address"): - cmd += " {address}".format(**set["extcommunity"]["vpn_distinguisher"]) + cmd += " {address}".format( + **set["extcommunity"]["vpn_distinguisher"], + ) if set["extcommunity"]["vpn_distinguisher"].get("additive"): cmd += " additive" command.append(cmd) @@ -525,7 +542,10 @@ def _tmplt_route_map_set_ip(config_data): command.append("{0} dynamic dhcp".format(cmd)) if set_ip["next_hop"].get("encapsulate"): command.append( - "{0} encapsulate l3vpn {encapsulate}".format(cmd, **set_ip["next_hop"]), + "{0} encapsulate l3vpn {encapsulate}".format( + cmd, + **set_ip["next_hop"], + ), ) if set_ip["next_hop"].get("peer_address"): command.append("{0} peer-address".format(cmd)) @@ -649,7 +669,10 @@ class Route_mapsTemplate(NetworkTemplate): "{{ route_map }}": { "route_map": "{{ route_map }}", "{{ action|d() + '_' + sequence|d() }}": { - "entries": {"action": "{{ action }}", "sequence": "{{ sequence }}"}, + "entries": { + "action": "{{ action }}", + "sequence": "{{ sequence }}", + }, }, }, }, @@ -691,7 +714,7 @@ class Route_mapsTemplate(NetworkTemplate): "result": { "{{ route_map }}": { "{{ action|d() + '_' + sequence|d() }}": { - "entries": {"description": "{{ description }}"}, + "entries": {"description": "'{{ description }}'"}, }, }, }, diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/snmp_server.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/snmp_server.py index 5107612eb..45cabaae9 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/snmp_server.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/snmp_server.py @@ -283,8 +283,7 @@ class Snmp_serverTemplate(NetworkTemplate): (\sread\s(?P<read>\S+))? (\swrite\s(?P<write>\S+))? (\snotify\s(?P<notify>\S+))? - (\saccess\s(?P<acl_v4>\S+))? - (\saccess\sipv6\s(?P<acl_v6>\S+))? + (\saccess(\sipv6\s(?P<acl_v6>\S+))?(\s(?P<acl_v4>\S+|\d+))?)? """, re.VERBOSE, ), "setval": "snmp-server group " @@ -296,8 +295,9 @@ class Snmp_serverTemplate(NetworkTemplate): "{{ (' read ' + read) if read is defined else '' }}" "{{ (' write ' + write) if write is defined else '' }}" "{{ (' notify ' + notify) if notify is defined else '' }}" - "{{ (' access ' + acl_v4) if acl_v4 is defined else '' }}" - "{{ (' access ipv6 ' + acl_v6) if acl_v6 is defined else '' }}", + "{{ (' access') if acl_v6 is defined or acl_v4 is defined else '' }}" + "{{ (' ipv6 ' + acl_v6) if acl_v6 is defined else '' }}" + "{{ (' ' + acl_v4|string) if acl_v4 is defined else '' }}", "result": { "groups": [ { @@ -398,8 +398,7 @@ class Snmp_serverTemplate(NetworkTemplate): (\sudp-port\s(?P<udp_port>\d+))? (\s(?P<version>v1|v3|v2c))? (\s(?P<version_option>encrypted))? - (\saccess\sipv6\s(?P<acl_v6>\S+))? - (\saccess\s(?P<acl_v4>\S+|\d+))? + (\saccess(\sipv6\s(?P<acl_v6>\S+))?(\s(?P<acl_v4>\S+|\d+))?)? (\svrf\s(?P<vrf>\S+))? """, re.VERBOSE, ), diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/vlans.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/vlans.py new file mode 100644 index 000000000..24907c868 --- /dev/null +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/vlans.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +# Copyright 2024 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 Vlans 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_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +def vlan_associated_config(config): + cmd = "" + if len(config.get("private_vlan", {}).get("associated")) > 1: + for vlan in config.get("private_vlan", {}).get("associated"): + cmd += str(vlan) + "," + cmd = cmd[:-1] + else: + cmd = config.get("private_vlan", {}).get("associated")[0] + return "private-vlan association " + cmd + + +class VlansTemplate(NetworkTemplate): + def __init__(self, lines=None, module=None): + super(VlansTemplate, self).__init__( + lines=lines, + tmplt=self, + module=module, + ) + + # fmt: off + PARSERS = [ + { + "name": "vlan_configuration", + "getval": re.compile( + r""" + ^vlan\sconfiguration\s(?P<vlan_id>\d+) + $""", re.VERBOSE, + ), + "setval": "vlan configuration {{ vlan_id|string }}", + "result": { + "{{ vlan_id }}": { + "vlan_id": "{{ vlan_id }}", + }, + }, + "shared": True, + }, + { + "name": "member", + "getval": re.compile( + r""" + \s*member + (\sevpn-instance\s(?P<inst_vlan_id>\d+))? + (\svni\s(?P<vni>\d+))? + $""", re.VERBOSE, + ), + "setval": "member" + "{{ (' evpn-instance ' + member.evi|string) if member.evi is defined else '' }}" + "{{ (' vni ' + member.vni|string) if member.vni is defined else '' }}", + "result": { + "{{ vlan_id }}": { + "member": { + "evi": "{{ inst_vlan_id }}", + "vni": "{{ vni }}", + }, + }, + }, + }, + { + "name": "vlans", + "getval": "", + "setval": "vlan {{ vlan_id|string }}", + "result": {}, + }, + { + "name": "name", + "getval": "", + "setval": "name {{ name|string }}", + "result": {}, + }, + { + "name": "state", + "getval": "", + "setval": "state {{ state }}", + "result": {}, + }, + { + "name": "mtu", + "getval": "", + "setval": "mtu {{ mtu|string }}", + "result": {}, + }, + { + "name": "remote_span", + "getval": "", + "setval": "remote-span", + "result": {}, + }, + { + "name": "private_vlan.type", + "getval": "", + "setval": "private-vlan {{ private_vlan.type if private_vlan.type is defined }}", + "result": {}, + }, + { + "name": "private_vlan.associated", + "getval": "", + "setval": vlan_associated_config, + "result": {}, + }, + { + "name": "shutdown", + "getval": "", + "setval": "shutdown", + "result": {}, + }, + ] + # fmt: on diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/utils/utils.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/utils/utils.py index f3038f7f2..1deea535b 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/utils/utils.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/utils/utils.py @@ -219,31 +219,19 @@ def validate_ipv4(value, module): address = value.split("/") if len(address) != 2: module.fail_json( - msg="address format is <ipv4 address>/<mask>, got invalid format {0}".format(value), + msg="address format is <ipv4 address>/<mask>, got invalid format {0}".format( + value, + ), ) if not is_masklen(address[1]): module.fail_json( - msg="invalid value for mask: {0}, mask should be in range 0-32".format(address[1]), + msg="invalid value for mask: {0}, mask should be in range 0-32".format( + address[1], + ), ) -def validate_ipv6(value, module): - if value: - address = value.split("/") - if len(address) != 2: - module.fail_json( - msg="address format is <ipv6 address>/<mask>, got invalid format {0}".format(value), - ) - else: - if not 0 <= int(address[1]) <= 128: - module.fail_json( - msg="invalid value for mask: {0}, mask should be in range 0-128".format( - address[1], - ), - ) - - def validate_n_expand_ipv4(module, want): # Check if input IPV4 is valid IP and expand IPV4 with its subnet mask ip_addr_want = want.get("address") |