diff --git a/lib/ansible/module_utils/network/common/cfg/base.py b/lib/ansible/module_utils/network/common/cfg/base.py index 8c1bae59340..5901dc767d4 100644 --- a/lib/ansible/module_utils/network/common/cfg/base.py +++ b/lib/ansible/module_utils/network/common/cfg/base.py @@ -13,6 +13,12 @@ from ansible.module_utils.network.common.network import get_resource_connection class ConfigBase(object): """ The base class for all resource modules """ + ACTION_STATES = ['merged', 'replaced', 'overridden', 'deleted'] + def __init__(self, module): self._module = module - self._connection = get_resource_connection(module) + self.state = module.params['state'] + self._connection = None + + if self.state not in ['rendered', 'parsed']: + self._connection = get_resource_connection(module) diff --git a/lib/ansible/module_utils/network/common/facts/facts.py b/lib/ansible/module_utils/network/common/facts/facts.py index b46299437a5..4c5ed72fefa 100644 --- a/lib/ansible/module_utils/network/common/facts/facts.py +++ b/lib/ansible/module_utils/network/common/facts/facts.py @@ -20,8 +20,9 @@ class FactsBase(object): self._warnings = [] self._gather_subset = module.params.get('gather_subset') self._gather_network_resources = module.params.get('gather_network_resources') - self._connection = get_resource_connection(module) - + self._connection = None + if module.params.get('state') not in ['rendered', 'parsed']: + self._connection = get_resource_connection(module) self.ansible_facts = {'ansible_network_resources': {}} self.ansible_facts['ansible_net_gather_network_resources'] = list() self.ansible_facts['ansible_net_gather_subset'] = list() diff --git a/lib/ansible/module_utils/network/eos/argspec/static_routes/__init__.py b/lib/ansible/module_utils/network/eos/argspec/static_routes/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/eos/argspec/static_routes/static_routes.py b/lib/ansible/module_utils/network/eos/argspec/static_routes/static_routes.py new file mode 100644 index 00000000000..8ad6c92e389 --- /dev/null +++ b/lib/ansible/module_utils/network/eos/argspec/static_routes/static_routes.py @@ -0,0 +1,109 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# +""" +The arg spec for the eos_static_routes module +""" + + +class Static_routesArgs(object): # pylint: disable=R0903 + """The arg spec for the eos_static_routes module + """ + def __init__(self, **kwargs): + pass + + argument_spec = { + 'config': { + 'elements': 'dict', + 'options': { + 'address_families': { + 'elements': 'dict', + 'options': { + 'afi': { + 'choices': ['ipv4', 'ipv6'], + 'required': True, + 'type': 'str' + }, + 'routes': { + 'elements': 'dict', + 'options': { + 'dest': { + 'required': True, + 'type': 'str' + }, + 'next_hops': { + 'elements': 'dict', + 'options': { + 'admin_distance': { + 'type': 'int' + }, + 'description': { + 'type': 'str' + }, + 'forward_router_address': { + 'type': 'str' + }, + 'interface': { + 'type': 'str' + }, + 'mpls_label': { + 'type': 'int' + }, + 'tag': { + 'type': 'int' + }, + 'track': { + 'type': 'str' + }, + 'vrf': { + 'type': 'str' + } + }, + 'type': 'list' + } + }, + 'type': 'list' + } + }, + 'type': 'list' + }, + 'vrf': { + 'type': 'str' + } + }, + 'type': 'list' + }, + 'running_config': { + 'type': 'str' + }, + 'state': { + 'choices': [ + 'deleted', 'merged', 'overridden', 'replaced', 'gathered', + 'rendered', 'parsed' + ], + 'default': + 'merged', + 'type': + 'str' + } + } # pylint: disable=C0301 diff --git a/lib/ansible/module_utils/network/eos/config/static_routes/__init__.py b/lib/ansible/module_utils/network/eos/config/static_routes/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/eos/config/static_routes/static_routes.py b/lib/ansible/module_utils/network/eos/config/static_routes/static_routes.py new file mode 100644 index 00000000000..e8de402ff6f --- /dev/null +++ b/lib/ansible/module_utils/network/eos/config/static_routes/static_routes.py @@ -0,0 +1,374 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The eos_static_routes class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" +from ansible.module_utils.network.common.cfg.base import ConfigBase +from ansible.module_utils.network.common.utils import to_list, dict_diff, remove_empties +from ansible.module_utils.network.eos.facts.facts import Facts +import re + + +class Static_routes(ConfigBase): + """ + The eos_static_routes class + """ + + gather_subset = [ + '!all', + '!min', + ] + + gather_network_resources = [ + 'static_routes', + ] + + def __init__(self, module): + super(Static_routes, self).__init__(module) + + def get_static_routes_facts(self, data=None): + """ Get the 'facts' (the current configuration) + + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources, data=data) + static_routes_facts = facts['ansible_network_resources'].get('static_routes') + if not static_routes_facts: + return [] + return static_routes_facts + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + warnings = list() + commands = list() + if self.state in self.ACTION_STATES: + existing_static_routes_facts = self.get_static_routes_facts() + else: + existing_static_routes_facts = [] + + if self.state in self.ACTION_STATES or self.state == 'rendered': + commands.extend(self.set_config(existing_static_routes_facts)) + + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + for command in commands: + self._connection.edit_config(command) + result['changed'] = True + if self.state in self.ACTION_STATES: + result['commands'] = commands + if self.state in self.ACTION_STATES or self.state == 'gathered': + changed_static_routes_facts = self.get_static_routes_facts() + elif self.state == 'rendered': + result['rendered'] = commands + elif self.state == 'parsed': + result['parsed'] = self.get_static_routes_facts(data=self._module.params['running_config']) + else: + changed_static_routes_facts = [] + + if self.state in self.ACTION_STATES: + result['before'] = existing_static_routes_facts + if result['changed']: + result['after'] = changed_static_routes_facts + elif self.state == 'gathered': + result['gathered'] = changed_static_routes_facts + + result['warnings'] = warnings + return result + + #changed_static_routes_facts = self.get_static_routes_facts() + + #result['before'] = existing_static_routes_facts + #if result['changed']: + # result['after'] = changed_static_routes_facts + + #result['warnings'] = warnings + #return result + + def set_config(self, existing_static_routes_facts): + """ Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + onbox_configs = [] + for h in existing_static_routes_facts: + return_command = add_commands(h) + for command in return_command: + onbox_configs.append(command) + config = self._module.params.get('config') + want = [] + if config: + for w in config: + want.append(remove_empties(w)) + have = existing_static_routes_facts + resp = self.set_state(want, have) + for want_config in resp: + if want_config not in onbox_configs: + commands.append(want_config) + return commands + + def set_state(self, want, have): + """ Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + state = self._module.params['state'] + if state == 'overridden': + commands = self._state_overridden(want, have) + elif state == 'deleted': + commands = self._state_deleted(want,have) + elif state == 'merged' or self.state == 'rendered': + commands = self._state_merged(want,have) + elif state == 'replaced': + commands = self._state_replaced(want, have) + return commands + + @staticmethod + def _state_replaced(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 = [] + haveconfigs = [] + vrf = get_vrf(want) + for h in have: + return_command = add_commands(h) + for command in return_command: + if vrf == "default": + if "vrf" not in command: + haveconfigs.append(command) + else: + if vrf in command: + haveconfigs.append(command) + wantconfigs = set_commands(want, have) + + removeconfigs = list(set(haveconfigs) - set(wantconfigs)) + for command in removeconfigs: + commands.append("no " + command) + for wantcmd in wantconfigs: + commands.append(wantcmd) + return commands + + @staticmethod + def _state_overridden(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 = [] + haveconfigs = [] + for h in have: + return_command = add_commands(h) + for command in return_command: + haveconfigs.append(command) + + wantconfigs = set_commands(want, have) + + removeconfigs = list(set(haveconfigs) - set(wantconfigs)) + for command in removeconfigs: + commands.append("no " + command) + for wantcmd in wantconfigs: + commands.append(wantcmd) + return commands + + @staticmethod + def _state_merged(want, have): + """ The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + return set_commands(want, have) + + @staticmethod + def _state_deleted(want,have): + """ The command generator when state is deleted + + :rtype: A list + :returns: the commands necessary to remove the current configuration + of the provided objects + """ + commands = [] + if not want: + for h in have: + return_command = add_commands(h) + for command in return_command: + command = "no " + command + commands.append(command) + else : + for w in want: + return_command = del_commands(w,have) + for command in return_command: + commands.append(command) + return commands + +def set_commands(want, have): + commands = [] + for w in want: + return_command = add_commands(w) + for command in return_command: + commands.append(command) + return commands + # if not obj_in_have: + # commands = self.add_commands(w) + # else: + # diff = self.diff_of_dicts(w, obj_in_have) + # commands = self.add_commands(diff) + # return commands + +def add_commands(want): + commandset = [] + if not want: + return commandset + vrf = want["vrf"] if "vrf" in want.keys() and want["vrf"] != "default" else None + for address_family in want["address_families"]: + for route in address_family["routes"]: + for next_hop in route["next_hops"]: + commands = [] + if address_family["afi"] == "ipv4": + commands.append('ip route') + else: + commands.append('ipv6 route') + if vrf: + commands.append(' vrf ' + vrf) + if not re.search(r'/', route["dest"]): + mask = route["dest"].split( )[1] + cidr = get_net_size(mask) + commands.append(' ' + route["dest"].split( )[0] + '/' + cidr) + else: + commands.append(' ' + route["dest"]) + commands.append(' ' + next_hop["interface"]) + if "forward_router_address" in next_hop.keys(): + commands.append(' ' + next_hop["forward_router_address"]) + if "mpls_label" in next_hop.keys(): + commands.append(' label ' + str(next_hop["mpls_label"])) + if "track" in next_hop.keys(): + commands.append(' track '+next_hop["track"]) + if "admin_distance" in next_hop.keys(): + commands.append(' '+str(next_hop["admin_distance"])) + if "description" in next_hop.keys(): + commands.append(' name '+str(next_hop["description"])) + if "tag" in next_hop.keys(): + commands.append(' tag '+str(next_hop["tag"])) + + config_commands = "".join(commands) + commandset.append(config_commands) + return commandset + +def del_commands(want,have): + commandset = [] + haveconfigs = [] + for h in have: + return_command = add_commands(h) + for command in return_command: + command = "no " + command + haveconfigs.append(command) + if want is None or "address_families" not in want.keys(): + commandset = haveconfigs + if "address_families" not in want.keys() and "vrf" in want.keys(): + commandset = [] + for command in haveconfigs: + if want["vrf"] in command: + commandset.append(command) + elif want is not None and "vrf" not in want.keys() and "address_families" not in want.keys(): + commandset = [] + for command in haveconfigs: + if "vrf" not in command: + commandset.append(command) + + elif want["address_families"]: + vrf = want["vrf"] if "vrf" in want.keys() and want["vrf"] else None + for address_family in want["address_families"]: + if "routes" not in address_family.keys(): + for command in haveconfigs: + afi = "ip " if address_family["afi"] == "ipv4" else "ipv6" + if afi in command: + if vrf: + if vrf in command: + commandset.append(command) + else: + commandset.append(command) + else: + for route in address_family["routes"]: + if not re.search(r'/', route["dest"]): + mask = route["dest"].split( )[1] + cidr = get_net_size(mask) + destination = route["dest"].split( )[0] + '/' + cidr + else: + destination = route["dest"] + if "next_hops" not in route.keys(): + for command in haveconfigs: + if destination in command: + if vrf: + if vrf in command: + commandset.append(command) + else: + commandset.append(command) + else: + for next_hop in route["next_hops"]: + commands = [] + if address_family["afi"] == "ipv4": + commands.append('no ip route') + else: + commands.append('no ipv6 route') + if vrf: + commands.append(' vrf ' + vrf) + commands.append(' ' + destination) + commands.append(' ' + next_hop["interface"]) + if "forward_router_address" in next_hop.keys(): + commands.append(' ' + next_hop["forward_router_address"]) + if "mpls_label" in next_hop.keys(): + commands.append(' label ' + str(next_hop["mpls_label"])) + if "track" in next_hop.keys(): + commands.append(' track '+next_hop["track"]) + if "admin_distance" in next_hop.keys(): + commands.append(' '+str(next_hop["admin_distance"])) + if "description" in next_hop.keys(): + commands.append(' name '+str(next_hop["description"])) + if "tag" in next_hop.keys(): + commands.append(' tag '+str(next_hop["tag"])) + + config_commands = "".join(commands) + commandset.append(config_commands) + return commandset + + +def get_net_size(netmask): + binary_str = '' + netmask = netmask.split('.') + for octet in netmask: + binary_str += bin(int(octet))[2:].zfill(8) + return str(len(binary_str.rstrip('0'))) + +def get_vrf(config): + vrf = "" + for c in config: + vrf = c["vrf"] if "vrf" in c.keys() and c["vrf"] else "default" + return vrf diff --git a/lib/ansible/module_utils/network/eos/facts/facts.py b/lib/ansible/module_utils/network/eos/facts/facts.py index 6dc786a2f7f..104cdeb65fe 100644 --- a/lib/ansible/module_utils/network/eos/facts/facts.py +++ b/lib/ansible/module_utils/network/eos/facts/facts.py @@ -21,6 +21,8 @@ from ansible.module_utils.network.eos.facts.lldp_global.lldp_global import Lldp_ from ansible.module_utils.network.eos.facts.lldp_interfaces.lldp_interfaces import Lldp_interfacesFacts from ansible.module_utils.network.eos.facts.vlans.vlans import VlansFacts from ansible.module_utils.network.eos.facts.legacy.base import Default, Hardware, Config, Interfaces +from ansible.module_utils.network.eos.facts.static_routes.static_routes import Static_routesFacts + FACT_LEGACY_SUBSETS = dict( @@ -39,6 +41,7 @@ FACT_RESOURCE_SUBSETS = dict( lldp_global=Lldp_globalFacts, lldp_interfaces=Lldp_interfacesFacts, vlans=VlansFacts, + static_routes=Static_routesFacts, ) diff --git a/lib/ansible/module_utils/network/eos/facts/static_routes/__init__.py b/lib/ansible/module_utils/network/eos/facts/static_routes/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/eos/facts/static_routes/static_routes.py b/lib/ansible/module_utils/network/eos/facts/static_routes/static_routes.py new file mode 100644 index 00000000000..368be552e63 --- /dev/null +++ b/lib/ansible/module_utils/network/eos/facts/static_routes/static_routes.py @@ -0,0 +1,202 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The eos static_routes fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" +import re +from copy import deepcopy + +from ansible.module_utils.network.common import utils +from ansible.module_utils.network.eos.argspec.static_routes.static_routes import Static_routesArgs + + +class Static_routesFacts(object): + """ The eos static_routes fact class + """ + + def __init__(self, module, subspec='config', options='options'): + self._module = module + self.argument_spec = Static_routesArgs.argument_spec + spec = deepcopy(self.argument_spec) + import q + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def get_device_data(self, connection): + return connection.get('show running-config | grep route') + + + def populate_facts(self, connection, ansible_facts, data=None): + """ Populate the facts for static_routes + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + if not data: + data = self.get_device_data(connection) + + import q + # split the config into instances of the resource + resource_delim = 'ip.* route' + find_pattern = r'(?:^|\n)%s.*?(?=(?:^|\n)%s|$)' % (resource_delim, + resource_delim) + resources = [p.strip() for p in re.findall(find_pattern, data)] + resources_without_vrf = [] + resource_vrf = {} + for resource in resources: + if resource and "vrf" not in resource: + resources_without_vrf.append(resource) + else: + vrf = re.search(r'ip(v6)* route vrf (.*?) .*',resource) + if vrf.group(2) in resource_vrf.keys(): + vrf_val = resource_vrf[vrf.group(2)] + vrf_val.append(resource) + resource_vrf.update({vrf.group(2): vrf_val}) + else : + resource_vrf.update({vrf.group(2): [resource]}) + resources_without_vrf = ["\n".join(resources_without_vrf)] + for vrf in resource_vrf.keys(): + vrflist = ["\n".join(resource_vrf[vrf])] + resource_vrf.update({vrf: vrflist}) + objs = [] + for resource in resources_without_vrf: + if resource: + obj = self.render_config(self.generated_spec, resource) + if obj: + objs.append(obj) + for resource in resource_vrf.keys(): + if resource: + obj = self.render_config(self.generated_spec, resource_vrf[resource][0]) + if obj: + objs.append(obj) + ansible_facts['ansible_network_resources'].pop('static_routes', None) + facts = {} + if objs: + facts['static_routes'] = [] + params = utils.validate_config(self.argument_spec, {'config': objs}) + for cfg in params['config']: + facts['static_routes'].append(utils.remove_empties(cfg)) + + + ansible_facts['ansible_network_resources'].update(facts) + return ansible_facts + + def render_config(self, spec, conf): + """ + Render config as dictionary structure and delete keys + from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + config = deepcopy(spec) + import q + address_family_dict = {} + route_dict = {} + dest_list = [] + afi_list = [] + vrf_list = [] + routes = [] + vrf_config_list = [] + config["address_families"] = [] + next_hops = {} + interface_list = ["Ethernet", "Loopback", "Management", "Nexthop-Group", + "Port-Channel", "Tunnel", "Vlan", "Vxlan", "vtep"] + conf_list = conf.split('\n') + for conf_elem in conf_list: + matches = re.findall(r'(ip|ipv6) route ([\d\.\/:]+|vrf) (.+)$', conf_elem) + if matches: + remainder = matches[0][2].split() + route_update = False + if matches[0][1] == "vrf": + vrf = remainder.pop(0) + # new vrf + if vrf not in vrf_list and vrf_list: + route_dict.update({"next_hops": next_hops}) + routes.append(route_dict) + address_family_dict.update({"routes": routes}) + config["address_families"].append(address_family_dict) + route_update = True + config.update({"vrf": vrf}) + vrf_list.append(vrf) + dest = remainder.pop(0) + else: + config["vrf"] = "default" + dest = matches[0][1] + afi = "ipv4" if matches[0][0] == "ip" else "ipv6" + if afi not in afi_list: + if afi_list and not route_update: + # new afi and not the first updating all prev configs + route_dict.update({"next_hops": next_hops}) + routes.append(route_dict) + address_family_dict.update({"routes": routes}) + config["address_families"].append(address_family_dict) + route_update = True + address_family_dict = {} + address_family_dict.update({"afi": afi}) + routes = [] + afi_list.append(afi) + # To check the format of the dest + prefix = re.search(r'/', dest) + if not prefix: + dest = dest + ' ' + remainder.pop(0) + if dest not in dest_list: + # For new dest and not the first dest + if dest_list and not route_update: + route_dict.update({"next_hops": next_hops}) + routes.append(route_dict) + dest_list.append(dest) + next_hops = [] + route_dict = {} + route_dict.update({"dest": dest}) + nexthops = {} + nxthop_addr = re.search(r'[\.\:]', remainder[0]) + if nxthop_addr: + nexthops.update({"interface": remainder.pop(0)}) + if remainder and remainder[0] == "label": + nexthops.update({"mpls_label": remainder.pop(1)}) + remainder.pop(0) + else: + interface = remainder.pop(0) + if interface in interface_list: + interface = interface +" "+ remainder.pop(0) + nexthops.update({"interface": interface}) + for attribute in remainder: + forward_addr = re.search(r'([\dA-Fa-f]+[:\.]+)+[\dA-Fa-f]+', attribute) + if forward_addr: + nexthops.update({"forward_router_address": remainder.pop(remainder.index(attribute))}) + for attribute in remainder: + for params in ["tag", "name", "track"]: + if attribute == params: + keyname = params + if attribute == "name": + keyname = "description" + nexthops.update({keyname: remainder.pop(remainder.index(attribute)+1)}) + remainder.pop(remainder.index(attribute)) + if remainder: + metric = re.search(r'\d+', remainder[0]) + if metric: + nexthops.update({"admin_distance": remainder.pop(0)}) + next_hops.append(nexthops) + route_dict.update({"next_hops": next_hops}) + routes.append(route_dict) + address_family_dict.update({"routes": routes}) + config["address_families"].append(address_family_dict) + return utils.remove_empties(config) diff --git a/lib/ansible/modules/network/eos/eos_static_routes.py b/lib/ansible/modules/network/eos/eos_static_routes.py new file mode 100644 index 00000000000..053079700c3 --- /dev/null +++ b/lib/ansible/modules/network/eos/eos_static_routes.py @@ -0,0 +1,363 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The module file for eos_static_routes +""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'network' +} + +DOCUMENTATION = """ +--- +module: eos_static_routes +version_added: 2.10 +short_description: Configures and manages attributes of static routes on Arista EOS platforms. +description: This module configures and manages the attributes of static routes on Arista EOS platforms. +author: Gomathi Selvi Srinivasan (@GomathiselviS) +notes: +options: + config: + description: + - A list of configurations for static routes. + type: list + elements: dict + suboptions: + vrf: + description: + - The VRF to which the static route(s) belong. + type: str + address_families: + description: A dictionary specifying the address family to which the static route(s) belong. + type: list + elements: dict + suboptions: + afi: + description: + - Specifies the top level address family indicator. + type: str + choices: ['ipv4', 'ipv6'] + required: True + routes: + description: A dictionary that specifies the static route configurations. + elements: dict + type: list + suboptions: + dest: + description: + - Destination IPv4 subnet (CIDR or address-mask notation). + - The address format is / or . + - The mask is number in range 0-32 for IPv4 and in range 0-128 for IPv6. + type: str + required: True + next_hops: + description: + - Details of route to be taken. + type: list + elements: dict + suboptions: + forward_router_address: + description: + - Forwarding router's address on destination interface. + type: str + interface: + description: + - Outgoing interface to take. For anything except 'null0', then next hop IP address should also be configured. + - IP address of the next hop router or + - null0 Null0 interface or + - ethernet e_num Ethernet interface or + - loopback l_num Loopback interface or + - management m_num Management interface or + - port-channel p_num + - vlan v_num + - vxlan vx_num + - Nexthop-Group Specify nexthop group name + - Tunnel Tunnel interface + - vtep Configure VXLAN Tunnel End Points + type: str + admin_distance: + description: + - Preference or administrative distance of route (range 1-255). + type: int + description: + description: + - Name of the static route. + type: str + tag: + description: + - Route tag value (ranges from 0 to 4294967295). + type: int + track: + description: + - Track value (range 1 - 512). Track must already be configured on the device before adding the route. + type: str + mpls_label: + description: + - MPLS label + type: int + vrf: + description: + - VRF of the destination. + type: str + state: + description: + - The state the configuration should be left in. + type: str + choices: + ['deleted', 'merged', 'overridden', 'replaced', 'gathered', 'rendered'] + default: + merged +""" +EXAMPLES = """ +# Using deleted + +Before State +------------- +veos(config)#show running-config | grep "route" +ip route 165.10.1.0/24 Ethernet1 100 +ip route 172.17.252.0/24 Nexthop-Group testgroup +ip route vrf testvrf 130.1.122.0/24 Ethernet1 tag 50 +ipv6 route 5001::/64 Ethernet1 50 +veos(config)# + +- name: Delete static route configuration + eos_static_routes: + state: deleted + +After State +----------- + +veos(config)#show running-config | grep "route" +veos(config)# + + +# Using merged + +Before State +------------- +veos(config)#show running-config | grep "route" +ip route 165.10.1.0/24 Ethernet1 100 +ip route 172.17.252.0/24 Nexthop-Group testgroup +ip route vrf testvrf 130.1.122.0/24 Ethernet1 tag 50 +ipv6 route 5001::/64 Ethernet1 50 +veos(config)# + +- name: Merge new static route configuration + eos_static_routes: + config: + - vrf: testvrf + address_families: + - afi: ipv6 + routes: + - dest: 2211::0/64 + next_hop: + - forward_router_address: 100:1::2 + interface: "Ethernet1" + state: merged + +After State +----------- + +veos(config)#show running-config | grep "route" +ip route 165.10.1.0/24 Ethernet1 100 +ip route 172.17.252.0/24 Nexthop-Group testgroup +ip route vrf testvrf 130.1.122.0/24 Ethernet1 tag 50 +ipv6 route 2211::/64 Ethernet1 100:1::2 +ipv6 route 5001::/64 Ethernet1 50 +veos(config)# + + +# Using overridden + +Before State +------------- +veos(config)#show running-config | grep "route" +ip route 165.10.1.0/24 Ethernet1 100 +ip route 172.17.252.0/24 Nexthop-Group testgroup +ip route vrf testvrf 130.1.122.0/24 Ethernet1 tag 50 +ipv6 route 5001::/64 Ethernet1 50 +veos(config)# + +- name: Overridden static route configuration + eos_static_routes: + config: + - vrf: testvrf + address_families: + - afi: ipv4 + routes: + - dest: 150.10.1.0/24 + next_hop: + - forward_router_address: 10.1.1.2 + interface: "Ethernet1" + state: replaced + +After State +----------- + +veos(config)#show running-config | grep "route" +ip route 150.10.1.0/24 Ethernet1 10.1.1.2 +veos(config)# + + +# Using replaced + +Before State +------------- +veos(config)#show running-config | grep "route" +ip route 165.10.1.0/24 Ethernet1 100 +ip route 172.17.252.0/24 Nexthop-Group testgroup +ip route vrf testvrf 130.1.122.0/24 Ethernet1 tag 50 +ipv6 route 5001::/64 Ethernet1 50 +veos(config)# + +- name: Replace static route configuration + eos_static_routes: + config: + - vrf: testvrf + address_families: + - afi: ipv4 + routes: + - dest: 165.10.1.0/24 + next_hop: + - forward_router_address: 10.1.1.2 + interface: "Ethernet1" + state: replaced + +After State +----------- + +veos(config)#show running-config | grep "route" +ip route 165.10.1.0/24 Ethernet1 10.1.1.2 +ip route 172.17.252.0/24 Nexthop-Group testgroup +ip route vrf testvrf 130.1.122.0/24 Ethernet1 tag 50 +ipv6 route 2211::/64 Ethernet1 100:1::2 +ipv6 route 5001::/64 Ethernet1 50 +veos(config)# + + +Before State +------------- +veos(config)#show running-config | grep "route" +ip route 165.10.1.0/24 Ethernet1 10.1.1.2 100 +ipv6 route 5001::/64 Ethernet1 +veos(config)# + + +- name: Gather the exisitng condiguration + eos_static_routes: + state: gathered + + +returns : + eos_static_routes: + config: + - address_families: + - afi: ipv4 + routes: + - dest: 165.10.1.0/24 + next_hop: + - forward_router_address: 10.1.1.2 + interface: "Ethernet1" + admin_distance: 100 + - afi: ipv6 + routes: + - dest: 5001::/64 + next_hop: + - interface: "Ethernet1" + + +# Using rendered + +i eos_static_routes: + config: + - address_families: + - afi: ipv4 + routes: + - dest: 165.10.1.0/24 + next_hop: + - forward_router_address: 10.1.1.2 + interface: "Ethernet1" + admin_distance: 100 + - afi: ipv6 + routes: + - dest: 5001::/64 + next_hop: + - interface: "Ethernet1" + + + +returns: + +ip route 165.10.1.0/24 Ethernet1 10.1.1.2 100 +ipv6 route 5001::/64 Ethernet1 + + +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The resulting configuration model invocation. + returned: when changed + sample: > + The configuration returned will always be in the same format + of the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: ['command 1', 'command 2', 'command 3'] +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network.eos.argspec.static_routes.static_routes import Static_routesArgs +from ansible.module_utils.network.eos.config.static_routes.static_routes import Static_routes + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule(argument_spec=Static_routesArgs.argument_spec, + supports_check_mode=True) + + result = Static_routes(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main()