summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authoromnom62 <omnom62@outlook.com>2026-06-19 17:37:25 +1000
committeromnom62 <omnom62@outlook.com>2026-06-19 17:37:25 +1000
commit88248610065245ea8ee5bbc299b116a37da584f3 (patch)
tree2ac90cdbb27b96b8a24c48ab9ce9fb93394c4c98 /plugins
parentdb8c9bdef5d524ce92c7068514a87a4b36a3ddd8 (diff)
downloadrest.vyos-88248610065245ea8ee5bbc299b116a37da584f3.tar.gz
rest.vyos-88248610065245ea8ee5bbc299b116a37da584f3.zip
T8989: vyos_interfaces
Diffstat (limited to 'plugins')
-rw-r--r--plugins/modules/vyos_interfaces.py334
1 files changed, 334 insertions, 0 deletions
diff --git a/plugins/modules/vyos_interfaces.py b/plugins/modules/vyos_interfaces.py
new file mode 100644
index 0000000..6ed45d5
--- /dev/null
+++ b/plugins/modules/vyos_interfaces.py
@@ -0,0 +1,334 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# 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
+
+DOCUMENTATION = r"""
+---
+module: vyos_interfaces
+short_description: Manage interface configuration on VyOS devices via REST API.
+description:
+ - Manages L2 interface configuration (description, MTU, speed, duplex, enabled)
+ on VyOS devices using the HTTPS REST API.
+ - IP address configuration is handled by M(vyos.rest.vyos_l3_interfaces).
+version_added: "1.0.0"
+author:
+ - VyOS Community (@vyos)
+options:
+ config:
+ description: List of interface configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Full interface name (e.g. eth0, bond0, lo).
+ type: str
+ required: true
+ description:
+ description: Interface description.
+ type: str
+ enabled:
+ description: Whether the interface is enabled. False sets the disable flag.
+ type: bool
+ default: true
+ mtu:
+ description: Interface MTU.
+ type: int
+ duplex:
+ description: Interface duplex setting.
+ type: str
+ choices: [auto, full, half]
+ speed:
+ description: Interface speed setting.
+ type: str
+ choices: [auto, "10", "100", "1000", "2500", "10000"]
+ state:
+ description:
+ - C(merged) - Merge config with existing interface settings.
+ - C(replaced) - Replace config for listed interfaces.
+ - C(overridden) - Replace config for all interfaces.
+ - C(deleted) - Remove listed interface config or all interface config.
+ - C(gathered) - Read interface config from device without changes.
+ type: str
+ choices: [merged, replaced, overridden, deleted, gathered]
+ default: merged
+seealso:
+ - module: vyos.vyos.vyos_interfaces
+ - module: vyos.rest.vyos_l3_interfaces
+"""
+
+EXAMPLES = r"""
+- name: Merge interface configuration
+ vyos.rest.vyos_interfaces:
+ config:
+ - name: eth0
+ description: Management interface
+ mtu: 1500
+ enabled: true
+ state: merged
+
+- name: Disable an interface
+ vyos.rest.vyos_interfaces:
+ config:
+ - name: eth1
+ enabled: false
+ state: merged
+
+- name: Delete interface description
+ vyos.rest.vyos_interfaces:
+ config:
+ - name: eth0
+ state: deleted
+
+- name: Gather current interface configuration
+ vyos.rest.vyos_interfaces:
+ state: gathered
+"""
+
+RETURN = r"""
+before:
+ description: Interface configuration before this module ran.
+ returned: always
+ type: list
+after:
+ description: Interface configuration after this module ran.
+ returned: when changed
+ type: list
+commands:
+ description: List of API command tuples sent to the device.
+ returned: always
+ type: list
+gathered:
+ description: Current interface configuration as structured data.
+ returned: when state is gathered
+ type: list
+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
+
+
+# Interface name prefix → API type key
+_IFACE_TYPE = {
+ "eth": "ethernet",
+ "bond": "bonding",
+ "lo": "loopback",
+ "tun": "tunnel",
+ "wg": "wireguard",
+ "vti": "vti",
+ "dum": "dummy",
+ "vtun": "openvpn",
+ "ppp": "pppoe",
+ "wlan": "wireless",
+ "br": "bridge",
+}
+
+# L2 fields managed by this module — excludes address, hw-id etc.
+_L2_FIELDS = ["description", "mtu", "duplex", "speed"]
+
+
+def _iface_type(name):
+ for prefix, itype in _IFACE_TYPE.items():
+ if name.startswith(prefix):
+ return itype
+ return "ethernet"
+
+
+def _iface_base(name):
+ return ["interfaces", _iface_type(name), name]
+
+
+def get_running_config(vyos):
+ raw = vyos.get_config(["interfaces"])
+ if not raw or not isinstance(raw, dict):
+ return []
+
+ result = []
+ for itype, ifaces in sorted(raw.items()):
+ if not isinstance(ifaces, dict):
+ continue
+ for iname, idata in sorted(ifaces.items()):
+ idata = idata or {}
+ entry = {"name": iname}
+ if idata.get("description"):
+ entry["description"] = idata["description"]
+ if "mtu" in idata:
+ entry["mtu"] = int(idata["mtu"])
+ if "duplex" in idata:
+ entry["duplex"] = idata["duplex"]
+ if "speed" in idata:
+ entry["speed"] = idata["speed"]
+ entry["enabled"] = "disable" not in idata
+ result.append(entry)
+
+ return result
+
+
+def _normalize(config):
+ """Convert argspec list to dict keyed by interface name."""
+ return {entry["name"]: entry for entry in (config or [])}
+
+
+def _iface_cmds(name, want, have):
+ """Generate set/delete commands to bring have → want for one interface."""
+ cmds = []
+ base = _iface_base(name)
+ have = have or {}
+
+ # description
+ want_desc = want.get("description")
+ have_desc = have.get("description")
+ if want_desc is not None and want_desc != have_desc:
+ cmds.append(("set", base + ["description", want_desc]))
+ elif want_desc is None and have_desc is not None:
+ cmds.append(("delete", base + ["description"]))
+
+ # mtu
+ want_mtu = want.get("mtu")
+ have_mtu = have.get("mtu")
+ if want_mtu is not None and want_mtu != have_mtu:
+ cmds.append(("set", base + ["mtu", str(want_mtu)]))
+
+ # duplex
+ want_duplex = want.get("duplex")
+ have_duplex = have.get("duplex")
+ if want_duplex is not None and want_duplex != have_duplex:
+ cmds.append(("set", base + ["duplex", want_duplex]))
+
+ # speed
+ want_speed = want.get("speed")
+ have_speed = have.get("speed")
+ if want_speed is not None and want_speed != have_speed:
+ cmds.append(("set", base + ["speed", want_speed]))
+
+ # enabled / disable flag
+ want_enabled = want.get("enabled", True)
+ have_enabled = have.get("enabled", True)
+ if not want_enabled and have_enabled:
+ cmds.append(("set", base + ["disable"]))
+ elif want_enabled and not have_enabled:
+ cmds.append(("delete", base + ["disable"]))
+
+ return cmds
+
+
+def _delete_iface_config(name, have):
+ """Generate delete commands to remove L2 config from an interface."""
+ cmds = []
+ base = _iface_base(name)
+ have = have or {}
+
+ for field in _L2_FIELDS:
+ if field in have:
+ cmds.append(("delete", base + [field]))
+ if not have.get("enabled", True):
+ cmds.append(("delete", base + ["disable"]))
+
+ return cmds
+
+
+def build_commands(config, have_raw, state):
+ cmds = []
+ have_map = _normalize(have_raw)
+
+ if state == "deleted":
+ if not config:
+ for name, have in have_map.items():
+ cmds += _delete_iface_config(name, have)
+ else:
+ for entry in config:
+ name = entry["name"]
+ cmds += _delete_iface_config(name, have_map.get(name, {}))
+ return cmds
+
+ want_map = _normalize(config)
+
+ if state == "overridden":
+ # delete L2 config from interfaces not in want
+ for name in set(have_map) - set(want_map):
+ cmds += _delete_iface_config(name, have_map[name])
+
+ for name, want in want_map.items():
+ have = have_map.get(name, {})
+
+ if state == "replaced":
+ # pre-check — only act if something differs
+ test_cmds = _iface_cmds(name, want, have)
+ if not test_cmds:
+ continue
+ # delete L2 fields then rebuild
+ cmds += _delete_iface_config(name, have)
+ have = {}
+
+ cmds += _iface_cmds(name, want, have if state != "replaced" else {})
+
+ return cmds
+
+
+ARGUMENT_SPEC = dict(
+ config=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ name=dict(type="str", required=True),
+ description=dict(type="str"),
+ enabled=dict(type="bool", default=True),
+ mtu=dict(type="int"),
+ duplex=dict(type="str", choices=["auto", "full", "half"]),
+ speed=dict(type="str", choices=["auto", "10", "100", "1000", "2500", "10000"]),
+ ),
+ ),
+ state=dict(
+ type="str",
+ default="merged",
+ choices=["merged", "replaced", "overridden", "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()