From 8e2d72cf77440346ef5802b9d3541e8e0cde965e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20W=C3=B6lfle?= Date: Wed, 1 Jan 2025 17:54:42 +0100 Subject: [PATCH] add module to manage Kea DHCP subnets Prior to this change, it was not possible to manage the subnets in the Kea DHCP service. I.e. it was not possible to add/remove subnets. This change adds a new module that allows to manage subnets. The code is split in two python files. 'plugins/modules/dhcp_subnet.py' and 'plugins/module_utils/main/dhcp_subnet_v4.py' --- meta/runtime.yml | 1 + plugins/module_utils/main/dhcp_subnet_v4.py | 46 ++++++++++++++++ plugins/modules/dhcp_subnet.py | 60 +++++++++++++++++++++ plugins/modules/list.py | 7 ++- 4 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 plugins/module_utils/main/dhcp_subnet_v4.py create mode 100644 plugins/modules/dhcp_subnet.py diff --git a/meta/runtime.yml b/meta/runtime.yml index 42762b2..64adf4c 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -127,6 +127,7 @@ action_groups: dhcp: - ansibleguy.opnsense.dhcp_reservation - ansibleguy.opnsense.dhcp_controlagent + - ansibleguy.opnsense.dhcp_subnet acme: - ansibleguy.opnsense.acme_general - ansibleguy.opnsense.acme_account diff --git a/plugins/module_utils/main/dhcp_subnet_v4.py b/plugins/module_utils/main/dhcp_subnet_v4.py new file mode 100644 index 0000000..0df85ca --- /dev/null +++ b/plugins/module_utils/main/dhcp_subnet_v4.py @@ -0,0 +1,46 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.main import \ + is_ip, is_network, is_unset +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.cls import BaseModule + +class SubnetV4(BaseModule): + FIELD_ID = 'subnet' + CMDS = { + 'add': 'addSubnet', + 'del': 'delSubnet', + 'set': 'setSubnet', + 'search': 'searchSubnet', + 'detail': 'getSubnet', + } + API_KEY_PATH = 'subnet4' + API_MOD = 'kea' + API_CONT = 'dhcpv4' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'subnet', 'description', 'pools' + ] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TYPING = {} + FIELDS_TRANSLATE = {} + EXIST_ATTR = 'subnet' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.subnet = {} + self.existing_subnets = None + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['subnet']): + self.m.fail_json( + "You need to provide the 'subnet' you want to create. E.g. (192.168.1.0/24)!" + ) + + if is_unset(self.p['pools']): + self.m.fail_json("You need to provide the IP 'pools' to be used in the subnet!") + + self._base_check() diff --git a/plugins/modules/dhcp_subnet.py b/plugins/modules/dhcp_subnet.py new file mode 100644 index 0000000..6df320c --- /dev/null +++ b/plugins/modules/dhcp_subnet.py @@ -0,0 +1,60 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2024, AnsibleGuy +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/kea.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.wrapper import module_wrapper + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.dhcp_subnet_v4 import SubnetV4 + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://opnsense.ansibleguy.net/modules/dhcp.html' +# EXAMPLES = 'https://opnsense.ansibleguy.net/modules/dhcp.html' + + +def run_module(): + module_args = dict( + subnet=dict(type='str', required=True, description='Subnet to create'), + description=dict(type='str', required=False), + pools=dict(type='str', required=True, description='IP address pools to offer in the subnet'), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(SubnetV4(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/list.py b/plugins/modules/list.py index 1aa02c5..92e7d17 100644 --- a/plugins/modules/list.py +++ b/plugins/modules/list.py @@ -36,7 +36,7 @@ 'ipsec_child', 'ipsec_vti', 'ipsec_auth_local', 'ipsec_auth_remote', 'frr_general', 'unbound_general', 'unbound_acl', 'ids_general', 'ids_policy', 'ids_rule', 'ids_ruleset', 'ids_user_rule', 'ids_policy_rule', 'openvpn_instance', 'openvpn_static_key', 'openvpn_client_override', 'dhcrelay_destination', 'dhcrelay_relay', - 'interface_lagg', 'interface_loopback', 'unbound_dnsbl', 'dhcp_reservation', 'acme_general', 'acme_account', + 'interface_lagg', 'interface_loopback', 'unbound_dnsbl', 'dhcp_reservation', 'dhcp_subnet', 'acme_general', 'acme_account', 'acme_validation', 'acme_action', 'acme_certificate', ] @@ -419,6 +419,11 @@ def run_module(): from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.acme_certificate import \ Certificate as Target_Obj + elif target == 'dhcp_subnet': + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.dhcp_subnet_v4 import \ + SubnetV4 as Target_Obj + + except AttributeError: module_dependency_error()