diff --git a/docs/source/modules/dhcrelay_destination.rst b/docs/source/modules/dhcrelay_destination.rst new file mode 100644 index 0000000..227885b --- /dev/null +++ b/docs/source/modules/dhcrelay_destination.rst @@ -0,0 +1,82 @@ +.. _modules_dhcrelay_destination: + +.. include:: ../_include/head.rst + +============================= +DHCRelay - Destinations +============================= + +**STATE**: stable + +**TESTS**: `Playbook `_ + +**API Docs**: `Core - DHCRelay `_ + +**Service Docs**: `DHCRelay `_ + + +Definition +********** + +.. csv-table:: Definition + :header: "Parameter", "Type", "Required", "Default", "Aliases", "Comment" + :widths: 15 10 10 10 10 45 + + "name","string","true","","\-","Unique name for this relay destination" + "server","list of strings","true","\-","\-","List of server IP addresses to relay DHCP requests to" + "reload","boolean","false","true","\-", .. include:: ../_include/param_reload.rst + +.. include:: ../_include/param_basic.rst + +Info +**** + +This module manages DHCRelay destinations. A destination can contain multiple IP addresses. + + +Examples +******** + +.. code-block:: yaml + + - hosts: localhost + gather_facts: no + module_defaults: + group/ansibleguy.opnsense.all: + firewall: 'opnsense.template.ansibleguy.net' + api_credential_file: '/home/guy/.secret/opn.key' + + ansibleguy.opnsense.list: + target: 'dhcrelay_destination' + + tasks: + - name: Example + ansibleguy.opnsense.dhcrelay_destination: + name: 'mydhcp' + server: + - '192.168.0.1' + # state: 'present' + # reload: true + # debug: false + + - name: Adding + ansibleguy.opnsense.dhcrelay_destination: + name: 'mydhcp' + server: + - '192.168.0.1' + + - name: Removing + ansibleguy.opnsense.dhcrelay_destination: + name: 'mydhcp' + server: + - '192.168.0.1' + state: 'absent' + + - name: Listing + ansibleguy.opnsense.list: + # target: 'dhcrelay_destination' + register: existing_entries + + - name: Printing dhcrelay destinations + ansible.builtin.debug: + var: existing_entries.data diff --git a/docs/source/modules/dhcrelay_relay.rst b/docs/source/modules/dhcrelay_relay.rst new file mode 100644 index 0000000..3a5c2c4 --- /dev/null +++ b/docs/source/modules/dhcrelay_relay.rst @@ -0,0 +1,83 @@ +.. _modules_dhcrelay_relay: + +.. include:: ../_include/head.rst + +============================= +DHCRelay - Relay +============================= + +**STATE**: stable + +**TESTS**: `Playbook `_ + +**API Docs**: `Core - DHCRelay `_ + +**Service Docs**: `DHCRelay `_ + + +Definition +********** + +.. csv-table:: Definition + :header: "Parameter", "Type", "Required", "Default", "Aliases", "Comment" + :widths: 15 10 10 10 10 45 + + "enabled","boolean","false","false","\-","Enable or disable this relay" + "interface","string","true","","i, int"," The interface to relay DHCP requests from" + "destination","string","true","\-","dest"," The destination server group to relay DHCP requests to" + "agent_info","boolean","false","false","\-","Add the relay agent information option" + "reload","boolean","false","true","\-", .. include:: ../_include/param_reload.rst + +.. include:: ../_include/param_basic.rst + +Info +**** + +This module manages DHCRelay relays. Each interface can be assigned a single relay. + + +Examples +******** + +.. code-block:: yaml + + - hosts: localhost + gather_facts: no + module_defaults: + group/ansibleguy.opnsense.all: + firewall: 'opnsense.template.ansibleguy.net' + api_credential_file: '/home/guy/.secret/opn.key' + + ansibleguy.opnsense.list: + target: 'dhcrelay_relay' + + tasks: + - name: Example + ansibleguy.opnsense.dhcrelay_relay: + interface: 'lan' + destination: mydhcp + # enabled: false + # agent_info: false + # state: 'present' + # reload: true + # debug: false + + - name: Adding + ansibleguy.opnsense.dhcrelay_relay: + interface: 'lan' + destination: mydhcp + + - name: Removing + ansibleguy.opnsense.dhcrelay_relay: + interface: 'lan' + destination: mydhcp + state: 'absent' + + - name: Listing + ansibleguy.opnsense.list: + # target: 'dhcrelay_relay' + register: existing_entries + + - name: Printing dhcrelay relays + ansible.builtin.debug: + var: existing_entries.data diff --git a/meta/runtime.yml b/meta/runtime.yml index 2306884..b4ac572 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -119,6 +119,9 @@ action_groups: - ansibleguy.opnsense.openvpn_client_override - ansibleguy.opnsense.openvpn_client_template - ansibleguy.opnsense.openvpn_client_export + dhcrelay: + - ansibleguy.opnsense.dhcrelay_destination + - ansibleguy.opnsense.dhcrelay_relay all: - metadata: extend_group: @@ -140,6 +143,7 @@ action_groups: - ansibleguy.opnsense.system - ansibleguy.opnsense.ids - ansibleguy.opnsense.openvpn + - ansibleguy.opnsense.dhcrelay plugin_routing: modules: diff --git a/plugins/module_utils/main/dhcrelay_destination.py b/plugins/module_utils/main/dhcrelay_destination.py new file mode 100644 index 0000000..7d5f631 --- /dev/null +++ b/plugins/module_utils/main/dhcrelay_destination.py @@ -0,0 +1,42 @@ +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_unset +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.cls import BaseModule + + +class DhcRelayDestination(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'addDest', + 'del': 'delDest', + 'set': 'setDest', + 'search': 'get', + } + API_KEY_PATH = 'dhcrelay.destinations' + API_KEY_PATH_REQ = 'destination' + API_MOD = 'dhcrelay' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + FIELDS_CHANGE = ['server'] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TYPING = { + 'list': ['server'], + } + EXIST_ATTR = 'destination' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.destination = {} + + def check(self) -> None: + + if self.p['state'] == 'present': + if is_unset(self.p['server']): + self.m.fail_json("You need to provide list of 'server' to create a dhcrelay_destination!") + + self._base_check() diff --git a/plugins/module_utils/main/dhcrelay_relay.py b/plugins/module_utils/main/dhcrelay_relay.py new file mode 100644 index 0000000..9430189 --- /dev/null +++ b/plugins/module_utils/main/dhcrelay_relay.py @@ -0,0 +1,58 @@ +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_unset +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.cls import BaseModule + + +class DhcRelayRelay(BaseModule): + FIELD_ID = 'interface' + CMDS = { + 'add': 'addRelay', + 'del': 'delRelay', + 'set': 'setRelay', + 'search': 'get', + 'detail': 'getRelay' + } + API_KEY_PATH = 'dhcrelay.relays' + API_KEY_PATH_REQ = 'relay' + API_MOD = 'dhcrelay' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + FIELDS_CHANGE = ['enabled', 'destination', 'agent_info'] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_VALUE_MAPPING = {} + FIELDS_TYPING = { + 'select': ['interface'], + 'select_opt_list': ['destination'], + 'bool': ['enabled', 'agent_info'] + } + EXIST_ATTR = 'relay' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.relay = {} + + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['destination']): + self.m.fail_json("You need to provide a 'destination' to create a dhcrelay_relay!") + + if self.p['state'] == 'present': + template = self.s.get({ + **self.call_cnf, + 'command': self.CMDS['detail'], + }) + + if template['relay']['destination']: + self.FIELDS_VALUE_MAPPING['destination'] = { + v['value']:k + for k,v in template['relay']['destination'].items() + } + + self._base_check() diff --git a/plugins/modules/dhcrelay_destination.py b/plugins/modules/dhcrelay_destination.py new file mode 100644 index 0000000..972eabf --- /dev/null +++ b/plugins/modules/dhcrelay_destination.py @@ -0,0 +1,66 @@ +#!/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/dhcrelay.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_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.dhcrelay_destination import \ + DhcRelayDestination + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://opnsense.ansibleguy.net/en/latest/modules/dhcrelay_destination.html' +# EXAMPLES = 'https://opnsense.ansibleguy.net/en/latest/modules/dhcrelay_destination.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, + description='A unique name for this relay destination.', + ), + server=dict( + type='list', elements='str', required=False, + description='A list of server IP addresses to relay DHCP requests to.' + ), + **RELOAD_MOD_ARG, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(DhcRelayDestination(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/dhcrelay_relay.py b/plugins/modules/dhcrelay_relay.py new file mode 100644 index 0000000..8bb0611 --- /dev/null +++ b/plugins/modules/dhcrelay_relay.py @@ -0,0 +1,73 @@ +#!/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/dhcrelay.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_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.dhcrelay_relay import DhcRelayRelay + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://opnsense.ansibleguy.net/en/latest/modules/dhcrelay_relay.html' +# EXAMPLES = 'https://opnsense.ansibleguy.net/en/latest/modules/dhcrelay_relay.html' + + +def run_module(): + module_args = dict( + enabled=dict( + type='bool', default=False, + description='Enable or disable this relay.', + ), + interface=dict( + type='str', required=True, aliases=['i', 'int'], + description='The interface to relay DHCP requests from. ' + ), + destination=dict( + type='str', required=False, aliases=['dest'], + description='The uuid of the destination server group to relay DHCP requests to.' + ), + agent_info=dict( + type='bool', default=False, + description='Add the relay agent information option.', + ), + **RELOAD_MOD_ARG, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(DhcRelayRelay(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 dd8d1d8..802ca18 100644 --- a/plugins/modules/list.py +++ b/plugins/modules/list.py @@ -35,7 +35,7 @@ 'webproxy_acl', 'webproxy_icap', 'webproxy_auth', 'nginx_upstream_server', 'ipsec_connection', 'ipsec_pool', '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', + 'openvpn_instance', 'openvpn_static_key', 'openvpn_client_override', 'dhcrelay_destination', 'dhcrelay_relay', ] @@ -378,6 +378,14 @@ def run_module(): from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.openvpn_client_override import \ Override as Target_Obj + elif target == 'dhcrelay_destination': + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.dhcrelay_destination import \ + DhcRelayDestination as Target_Obj + + elif target == 'dhcrelay_relay': + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.dhcrelay_relay import \ + DhcRelayRelay as Target_Obj + except AttributeError: module_dependency_error() diff --git a/scripts/test.sh b/scripts/test.sh index 40ccc94..12d177d 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -149,6 +149,8 @@ run_test 'nginx_general' 1 run_test 'nginx_upstream_server' 1 run_test 'system' 1 run_test 'package' 1 +run_test 'dhcrelay_destination' 1 +run_test 'dhcrelay_relay' 1 echo '' echo '##############################' diff --git a/tests/dhcrelay_destination.yml b/tests/dhcrelay_destination.yml new file mode 100644 index 0000000..2159baf --- /dev/null +++ b/tests/dhcrelay_destination.yml @@ -0,0 +1,115 @@ +--- + +# todo: test default matching + +- name: Testing DHCRelay Destination + hosts: localhost + gather_facts: no + module_defaults: + group/ansibleguy.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + ansibleguy.opnsense.list: + target: 'dhcrelay_destination' + + tasks: + - name: Listing + ansibleguy.opnsense.list: + register: opn1 + failed_when: > + 'data' not in opn1 or + opn1.data | length != 0 + + - name: Removing - does not exist + ansibleguy.opnsense.dhcrelay_destination: + name: 'mydhcp' + state: 'absent' + reload: false + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + + - name: Adding - failing because of invalid values + ansibleguy.opnsense.dhcrelay_destination: + name: 'mydhcp' + server: '{{ item }}' + reload: false + register: opn_fail1 + failed_when: not opn_fail1.failed + loop: + - ['dhcp'] + - ['172.0.0'] + when: not ansible_check_mode + + - name: Adding 1 + ansibleguy.opnsense.dhcrelay_destination: + name: 'mydhcp' + server: + - '192.168.254.254' + reload: false # speed + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Adding 2 + ansibleguy.opnsense.dhcrelay_destination: + name: 'myotherdhcp' + server: + - '192.168.254.252' + - '192.168.254.253' + reload: false # speed + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Adding 2 - nothing changed + ansibleguy.opnsense.dhcrelay_destination: + name: 'myotherdhcp' + server: + - '192.168.254.252' + - '192.168.254.253' + reload: false # speed + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + when: not ansible_check_mode + + - name: Removing 2 + ansibleguy.opnsense.dhcrelay_destination: + name: 'myotherdhcp' + state: 'absent' + reload: false # speed + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Listing + ansibleguy.opnsense.list: + register: opn7 + failed_when: > + 'data' not in opn7 or + opn7.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + ansibleguy.opnsense.dhcrelay_destination: + name: 'mydhcp' + state: 'absent' + reload: false # speed + when: not ansible_check_mode + + - name: Listing + ansibleguy.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 0 + when: not ansible_check_mode diff --git a/tests/dhcrelay_relay.yml b/tests/dhcrelay_relay.yml new file mode 100644 index 0000000..c1f0981 --- /dev/null +++ b/tests/dhcrelay_relay.yml @@ -0,0 +1,137 @@ +--- +- name: Testing DHCRelay Relay + hosts: localhost + gather_facts: no + module_defaults: + group/ansibleguy.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + ansibleguy.opnsense.list: + target: 'dhcrelay_relay' + + tasks: + - name: Listing + ansibleguy.opnsense.list: + register: opn1 + failed_when: > + 'data' not in opn1 or + opn1.data | length != 0 + + - name: Removing - does not exist + ansibleguy.opnsense.dhcrelay_relay: + interface: 'DOESNOTEXIST' + state: 'absent' + reload: false + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + + - name: Adding - failing because of invalid interface + ansibleguy.opnsense.dhcrelay_relay: + interface: 'DOESNOTEXIST' + server: 'mydhcp' + reload: false + register: opn_fail1 + failed_when: not opn_fail1.failed + when: not ansible_check_mode + + - name: Adding - failing because of invalid destination + ansibleguy.opnsense.dhcrelay_relay: + interface: 'lan' + destination: 'DOESNOTEXIST' + reload: false + register: opn_fail2 + failed_when: not opn_fail2.failed + when: not ansible_check_mode + + - name: Adding destination + ansibleguy.opnsense.dhcrelay_destination: + name: 'mydhcp' + server: + - '192.168.254.254' + reload: false # speed + when: not ansible_check_mode + + - name: Adding 1 + ansibleguy.opnsense.dhcrelay_relay: + interface: 'lan' + destination: 'mydhcp' + reload: false # speed + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Adding 1 - nothing changed + ansibleguy.opnsense.dhcrelay_relay: + interface: 'lan' + destination: 'mydhcp' + reload: false # speed + debug: true + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + ansibleguy.opnsense.dhcrelay_relay: + interface: 'lan' + destination: 'mydhcp' + enabled: true + reload: false # speed + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Enabling 1 - nothing changed + ansibleguy.opnsense.dhcrelay_relay: + interface: 'lan' + destination: 'mydhcp' + enabled: true + reload: false # speed + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Listing + ansibleguy.opnsense.list: + register: opn5 + failed_when: > + 'data' not in opn5 or + opn5.data | length != 1 + when: not ansible_check_mode + + - name: Removing + ansibleguy.opnsense.dhcrelay_relay: + interface: 'lan' + state: 'absent' + reload: false # speed + when: not ansible_check_mode + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Cleanup destination + ansibleguy.opnsense.dhcrelay_destination: + name: 'mydhcp' + state: 'absent' + reload: false # speed + when: not ansible_check_mode + + - name: Listing + ansibleguy.opnsense.list: + register: opn7 + failed_when: > + 'data' not in opn7 or + opn7.data | length != 0 + when: not ansible_check_mode