summaryrefslogtreecommitdiff
path: root/plugins/modules
diff options
context:
space:
mode:
authoromnom62 <omnom62@outlook.com>2026-06-29 08:52:51 +1000
committeromnom62 <omnom62@outlook.com>2026-06-29 08:52:51 +1000
commita0393f9be79bdb45e7a4dcd51b50cdb3d840988c (patch)
treec9679c09d6a6b163c01fe4c049fd7a3d8bc6fe28 /plugins/modules
parent6be14447461593f2c71088b54fe523cb468e32dd (diff)
downloadrest.vyos-a0393f9be79bdb45e7a4dcd51b50cdb3d840988c.tar.gz
rest.vyos-a0393f9be79bdb45e7a4dcd51b50cdb3d840988c.zip
T8989: ospf_v3 module
Diffstat (limited to 'plugins/modules')
-rw-r--r--plugins/modules/vyos_ospfv3.py360
1 files changed, 360 insertions, 0 deletions
diff --git a/plugins/modules/vyos_ospfv3.py b/plugins/modules/vyos_ospfv3.py
new file mode 100644
index 0000000..fcf404e
--- /dev/null
+++ b/plugins/modules/vyos_ospfv3.py
@@ -0,0 +1,360 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# GNU General Public License v3.0+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = r"""
+---
+module: vyos_ospfv3
+short_description: Manage OSPFv3 configuration on VyOS devices using REST API
+description:
+ - Manages OSPFv3 configuration on VyOS devices via the REST API.
+ - Uses REST API (C(connection=httpapi)) instead of CLI.
+version_added: "1.0.0"
+author:
+ - VyOS Community (@vyos)
+options:
+ config:
+ description: OSPFv3 configuration.
+ type: dict
+ suboptions:
+ areas:
+ description: OSPFv3 areas.
+ type: list
+ elements: dict
+ suboptions:
+ area_id:
+ description: Area identity.
+ type: str
+ required: true
+ export_list:
+ description: Name of export-list.
+ type: str
+ import_list:
+ description: Name of import-list.
+ type: str
+ range:
+ description: Summarize routes matching prefix.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description: IPv6 prefix.
+ type: str
+ required: true
+ advertise:
+ description: Advertise this range.
+ type: bool
+ not_advertise:
+ description: Do not advertise this range.
+ type: bool
+ parameters:
+ description: OSPFv3 global parameters.
+ type: dict
+ suboptions:
+ router_id:
+ description: Router ID (IPv4 address format).
+ type: str
+ redistribute:
+ description: Redistribute routes from another protocol.
+ type: list
+ elements: dict
+ suboptions:
+ route_type:
+ description: Protocol to redistribute.
+ type: str
+ choices: [bgp, connected, kernel, ripng, static]
+ route_map:
+ description: Route map to apply.
+ type: str
+ state:
+ description:
+ - Desired state of the OSPFv3 configuration.
+ - C(merged) adds or updates without removing existing config.
+ - C(replaced) replaces the entire OSPFv3 configuration.
+ - C(deleted) removes OSPFv3 configuration.
+ - C(gathered) returns current configuration as structured data.
+ type: str
+ choices: [merged, replaced, deleted, gathered]
+ default: merged
+notes:
+ - Requires C(ansible_connection=httpapi) with the VyOS httpapi plugin.
+ - C(ansible_network_os) must be set to C(vyos.rest.vyos).
+"""
+
+EXAMPLES = r"""
+- name: Merge OSPFv3 configuration
+ vyos.rest.vyos_ospfv3:
+ config:
+ parameters:
+ router_id: 192.0.2.10
+ redistribute:
+ - route_type: bgp
+ areas:
+ - area_id: "2"
+ export_list: export1
+ import_list: import1
+ range:
+ - address: "2001:db10::/32"
+ - address: "2001:db20::/32"
+ state: merged
+
+- name: Delete all OSPFv3 configuration
+ vyos.rest.vyos_ospfv3:
+ state: deleted
+
+- name: Gather current OSPFv3 configuration
+ vyos.rest.vyos_ospfv3:
+ state: gathered
+"""
+
+RETURN = r"""
+before:
+ description: OSPFv3 configuration before this module ran.
+ returned: always
+ type: dict
+after:
+ description: OSPFv3 configuration after this module ran.
+ returned: when changed
+ type: dict
+commands:
+ description: List of API command tuples sent to the device.
+ returned: always
+ type: list
+gathered:
+ description: Current OSPFv3 configuration as structured data.
+ returned: when state is gathered
+ type: dict
+saved:
+ description: Whether the config was saved after changes.
+ returned: when changes are applied
+ type: bool
+response:
+ description: Raw API response.
+ returned: when changes are applied
+ type: dict
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.vyos.rest.plugins.module_utils.vyos import VyOSModule
+
+
+_BASE = ["protocols", "ospfv3"]
+
+
+def get_running_config(vyos):
+ raw = vyos.get_config(_BASE)
+ if not raw or not isinstance(raw, dict):
+ return {}
+ return _parse_ospfv3(raw)
+
+
+def _parse_ospfv3(raw):
+ result = {}
+
+ # parameters
+ params = raw.get("parameters", {})
+ if params:
+ result["parameters"] = {}
+ if "router-id" in params:
+ result["parameters"]["router_id"] = params["router-id"]
+
+ # redistribute
+ redist_raw = raw.get("redistribute", {})
+ if redist_raw and isinstance(redist_raw, dict):
+ redist = []
+ for route_type, data in sorted(redist_raw.items()):
+ entry = {"route_type": route_type}
+ if isinstance(data, dict) and data.get("route-map"):
+ entry["route_map"] = data["route-map"]
+ redist.append(entry)
+ if redist:
+ result["redistribute"] = redist
+
+ # areas
+ area_raw = raw.get("area", {})
+ if area_raw and isinstance(area_raw, dict):
+ areas = []
+ for area_id, area_data in sorted(area_raw.items()):
+ area = {"area_id": area_id}
+ area_data = area_data or {}
+ if area_data.get("export-list"):
+ area["export_list"] = area_data["export-list"]
+ if area_data.get("import-list"):
+ area["import_list"] = area_data["import-list"]
+ range_raw = area_data.get("range", {})
+ if range_raw and isinstance(range_raw, dict):
+ ranges = []
+ for prefix, rdata in sorted(range_raw.items()):
+ r = {"address": prefix}
+ rdata = rdata or {}
+ if "advertise" in rdata:
+ r["advertise"] = True
+ if "not-advertise" in rdata:
+ r["not_advertise"] = True
+ ranges.append(r)
+ if ranges:
+ area["range"] = ranges
+ areas.append(area)
+ if areas:
+ result["areas"] = areas
+
+ return result
+
+
+def build_commands(config, have, state):
+ cmds = []
+
+ if state == "deleted":
+ if have:
+ cmds.append(("delete", _BASE))
+ return cmds
+
+ if state == "replaced":
+ # Build what we would set from scratch and compare to have
+ would_set = build_commands(config, {}, "merged")
+ have_set = build_commands(have, {}, "merged")
+ if would_set == have_set:
+ return []
+ if have:
+ cmds.append(("delete", _BASE))
+ have = {}
+
+ # parameters
+ want_params = (config or {}).get("parameters") or {}
+ have_params = have.get("parameters") or {}
+ if want_params.get("router_id") and want_params["router_id"] != have_params.get("router_id"):
+ cmds.append(("set", _BASE + ["parameters", "router-id", want_params["router_id"]]))
+
+ # redistribute
+ want_redist = {r["route_type"]: r for r in ((config or {}).get("redistribute") or [])}
+ have_redist = {r["route_type"]: r for r in (have.get("redistribute") or [])}
+
+ for rt in set(have_redist) - set(want_redist):
+ if state == "merged":
+ pass # merged doesn't remove
+ for rt, entry in want_redist.items():
+ if rt not in have_redist:
+ cmds.append(("set", _BASE + ["redistribute", rt]))
+ if entry.get("route_map"):
+ have_rm = have_redist.get(rt, {}).get("route_map")
+ if entry["route_map"] != have_rm:
+ cmds.append(("set", _BASE + ["redistribute", rt, "route-map", entry["route_map"]]))
+
+ # areas
+ want_areas = {a["area_id"]: a for a in ((config or {}).get("areas") or [])}
+ have_areas = {a["area_id"]: a for a in (have.get("areas") or [])}
+
+ for area_id, want_area in want_areas.items():
+ have_area = have_areas.get(area_id, {})
+ abase = _BASE + ["area", area_id]
+
+ if want_area.get("export_list") and want_area["export_list"] != have_area.get(
+ "export_list",
+ ):
+ cmds.append(("set", abase + ["export-list", want_area["export_list"]]))
+ if want_area.get("import_list") and want_area["import_list"] != have_area.get(
+ "import_list",
+ ):
+ cmds.append(("set", abase + ["import-list", want_area["import_list"]]))
+
+ want_ranges = {r["address"]: r for r in (want_area.get("range") or [])}
+ have_ranges = {r["address"]: r for r in (have_area.get("range") or [])}
+
+ for addr in want_ranges:
+ if addr not in have_ranges:
+ cmds.append(("set", abase + ["range", addr]))
+ r = want_ranges[addr]
+ if r.get("not_advertise"):
+ cmds.append(("set", abase + ["range", addr, "not-advertise"]))
+ elif r.get("advertise"):
+ cmds.append(("set", abase + ["range", addr, "advertise"]))
+
+ return cmds
+
+
+ARGUMENT_SPEC = dict(
+ config=dict(
+ type="dict",
+ options=dict(
+ areas=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ area_id=dict(type="str", required=True),
+ export_list=dict(type="str"),
+ import_list=dict(type="str"),
+ range=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ address=dict(type="str", required=True),
+ advertise=dict(type="bool"),
+ not_advertise=dict(type="bool"),
+ ),
+ ),
+ ),
+ ),
+ parameters=dict(
+ type="dict",
+ options=dict(
+ router_id=dict(type="str"),
+ ),
+ ),
+ redistribute=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ route_type=dict(
+ type="str",
+ choices=["bgp", "connected", "kernel", "ripng", "static"],
+ ),
+ route_map=dict(type="str"),
+ ),
+ ),
+ ),
+ ),
+ state=dict(
+ default="merged",
+ choices=["merged", "replaced", "deleted", "gathered"],
+ ),
+)
+
+
+def main():
+ module = AnsibleModule(ARGUMENT_SPEC, supports_check_mode=True)
+ vyos = VyOSModule(module)
+
+ state = module.params["state"]
+ config = module.params.get("config") or {}
+
+ have = get_running_config(vyos)
+
+ if state == "gathered":
+ module.exit_json(changed=False, gathered=have)
+
+ commands = build_commands(config, have, state)
+
+ if module.check_mode:
+ module.exit_json(changed=bool(commands), commands=commands, before=have)
+
+ if commands:
+ response = vyos.apply_commands(commands)
+ saved = vyos.save_config()
+ module.exit_json(
+ changed=True,
+ before=have,
+ after=get_running_config(vyos),
+ commands=commands,
+ saved=saved,
+ response=response,
+ )
+
+ module.exit_json(changed=False, before=have, after=have, commands=[])
+
+
+if __name__ == "__main__":
+ main()