-
-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #135 from woelfle/manage-dhcp-subnets
add module to manage Kea DHCP subnets
- Loading branch information
Showing
11 changed files
with
477 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
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 \ | ||
get_selected_list, simplify_translate | ||
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.cls import BaseModule | ||
|
||
|
||
class SubnetV4(BaseModule): | ||
CMDS = { | ||
'add': 'addSubnet', | ||
'del': 'delSubnet', | ||
'set': 'setSubnet', | ||
'search': 'searchSubnet', | ||
'detail': 'getSubnet', | ||
} | ||
API_KEY = 'subnet4' | ||
API_KEY_PATH = 'subnet4' | ||
API_MOD = 'kea' | ||
API_CONT = 'dhcpv4' | ||
API_CONT_REL = 'service' | ||
FIELDS_CHANGE = [ | ||
'subnet', 'description', 'pools', 'auto_options', | ||
] | ||
FIELDS_ALL = FIELDS_CHANGE | ||
FIELDS_TYPING = { | ||
'list': ['gateway', 'dns', 'domain_search', 'ntp_servers', 'time_servers'], # 'pools', | ||
'bool': ['auto_options'], | ||
} | ||
FIELDS_TRANSLATE = { | ||
'auto_options': 'option_data_autocollect', | ||
} | ||
API_ATTR_OPTIONS = 'option_data' | ||
API_FIELDS_OPTIONS = [ | ||
'gateway', 'routes', 'dns', 'domain', 'domain_search', 'ntp_servers', 'time_servers', | ||
'next_server', 'tftp_server', 'tftp_file', | ||
] | ||
POOL_JOIN_CHAR = '\n' | ||
FIELDS_TRANSLATE_SPECIAL = { | ||
'dns': 'domain_name_servers', | ||
'domain': 'domain_name', | ||
'gateway': 'routers', | ||
'routes': 'static_routes', | ||
'tftp_server': 'tftp_server_name', | ||
'tftp_file': 'boot_file_name', | ||
} | ||
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 _simplify_existing(self, entry: dict) -> dict: | ||
simple = simplify_translate( | ||
existing=entry, | ||
typing=self.FIELDS_TYPING, | ||
translate=self.FIELDS_TRANSLATE, | ||
ignore=self.API_FIELDS_OPTIONS, | ||
) | ||
|
||
simple['pools'] = simple['pools'].split(self.POOL_JOIN_CHAR) | ||
opts = entry[self.API_ATTR_OPTIONS] | ||
if isinstance(opts, dict): | ||
simple['dns'] = get_selected_list(opts[self.FIELDS_TRANSLATE_SPECIAL['dns']]) | ||
simple['domain_search'] = get_selected_list(opts['domain_search']) | ||
simple['gateway'] = get_selected_list(opts[self.FIELDS_TRANSLATE_SPECIAL['gateway']]) | ||
simple['routes'] = opts[self.FIELDS_TRANSLATE_SPECIAL['routes']] | ||
simple['domain'] = opts[self.FIELDS_TRANSLATE_SPECIAL['domain']] | ||
simple['ntp_servers'] = get_selected_list(opts['ntp_servers']) | ||
simple['time_servers'] = get_selected_list(opts['time_servers']) | ||
simple['tftp_server'] = opts[self.FIELDS_TRANSLATE_SPECIAL['tftp_server']] | ||
simple['tftp_file'] = opts[self.FIELDS_TRANSLATE_SPECIAL['tftp_file']] | ||
|
||
else: | ||
opt_keys = list(self.FIELDS_TRANSLATE_SPECIAL.keys()) | ||
opt_keys.extend(['domain_search', 'ntp_servers', 'time_servers']) | ||
|
||
for opt in opt_keys: | ||
if opt in self.FIELDS_TYPING['list']: | ||
simple[opt] = [] | ||
|
||
else: | ||
simple[opt] = '' | ||
|
||
return simple | ||
|
||
def _build_request(self) -> dict: | ||
raw_request = self.b.build_request(ignore_fields=self.API_FIELDS_OPTIONS) | ||
|
||
raw_request[self.API_KEY]['pools'] = self.POOL_JOIN_CHAR.join(self.p['pools']) | ||
raw_request[self.API_KEY][self.API_ATTR_OPTIONS] = { | ||
self.FIELDS_TRANSLATE_SPECIAL['dns']: self.b.RESP_JOIN_CHAR.join(self.p['dns']), | ||
self.FIELDS_TRANSLATE_SPECIAL['gateway']: self.b.RESP_JOIN_CHAR.join(self.p['gateway']), | ||
self.FIELDS_TRANSLATE_SPECIAL['routes']: self.p['routes'], | ||
self.FIELDS_TRANSLATE_SPECIAL['domain']: self.p['domain'], | ||
self.FIELDS_TRANSLATE_SPECIAL['tftp_server']: self.p['tftp_server'], | ||
self.FIELDS_TRANSLATE_SPECIAL['tftp_file']: self.p['tftp_file'], | ||
'ntp_servers': self.b.RESP_JOIN_CHAR.join(self.p['ntp_servers']), | ||
'time_servers': self.b.RESP_JOIN_CHAR.join(self.p['time_servers']), | ||
'domain_search': self.b.RESP_JOIN_CHAR.join(self.p['domain_search']), | ||
} | ||
|
||
return raw_request |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
#!/usr/bin/python | ||
# -*- coding: utf-8 -*- | ||
|
||
# Copyright: (C) 2024, AnsibleGuy <[email protected]> | ||
# 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 use, should be large enough to hold the specified pools and reservations', | ||
), | ||
description=dict( | ||
type='str', required=False, aliases=['desc'], default='', | ||
), | ||
pools=dict( | ||
type='list', elements='str', required=False, default=[], | ||
description='List of pools, one per line in range or subnet format ' | ||
'(e.g. 192.168.0.100 - 192.168.0.200 , 192.0.2.64/26)' | ||
), | ||
auto_options=dict( | ||
type='bool', required=False, default=True, aliases=['option_data_autocollect'], | ||
description='Automatically update option data for relevant attributes as routers, ' | ||
'dns servers and ntp servers when applying settings from the gui.' | ||
), | ||
gateway=dict( | ||
type='list', elements='str', required=False, aliases=['gw', 'routers'], default=[], | ||
description='Default gateways to offer to the clients', | ||
), | ||
routes=dict( | ||
type='str', required=False, aliases=['static_routes'], default='', | ||
description='Static routes that the client should install in its routing cache, ' | ||
'defined as dest-ip1,router-ip1;dest-ip2,router-ip2', | ||
), | ||
dns=dict( | ||
type='list', elements='str', required=False, aliases=['dns_servers', 'dns_srv'], default=[], | ||
description='DNS servers to offer to the clients', | ||
), | ||
domain=dict( | ||
type='str', required=False, aliases=['domain_name', 'dom_name', 'dom'], default='', | ||
description="The domain name to offer to the client, set to this firewall's domain name when left empty", | ||
), | ||
domain_search=dict( | ||
type='list', elements='str', required=False, aliases=['dom_search'], default=[], | ||
description="Specifies a ´search list´ of Domain Names to be used by the client to locate " | ||
'not-fully-qualified domain names.', | ||
), | ||
ntp_servers=dict( | ||
type='list', elements='str', required=False, aliases=['ntp_srv', 'ntp'], default=[], | ||
description='Specifies a list of IP addresses indicating NTP (RFC 5905) servers available to the client.', | ||
), | ||
time_servers=dict( | ||
type='list', elements='str', required=False, aliases=['time_srv'], default=[], | ||
description='Specifies a list of RFC 868 time servers available to the client.', | ||
), | ||
next_server=dict( | ||
type='str', required=False, aliases=['next_srv'], default='', | ||
description='Next server IP address', | ||
), | ||
tftp_server=dict( | ||
type='str', required=False, aliases=['tftp', 'tftp_srv', 'tftp_server_name'], default='', | ||
description='TFTP server address or fqdn', | ||
), | ||
tftp_file=dict( | ||
type='str', required=False, aliases=['tftp_boot_file', 'boot_file_name'], default='', | ||
description='TFTP Boot filename to request', | ||
), | ||
ipv=dict(type='int', required=False, default=4, choices=[4, 6], aliases=['ip_version']), | ||
match_fields=dict( | ||
type='list', required=False, elements='str', | ||
description='Fields that are used to match configured interface with the running config - ' | ||
"if any of those fields are changed, the module will think it's a new entry", | ||
choices=['subnet', 'description'], | ||
default=['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, | ||
) | ||
|
||
if module.params['ipv'] == 6: | ||
module.fail_json('DHCPv6 is not yet supported!') | ||
|
||
module_wrapper(SubnetV4(module=module, result=result)) | ||
module.exit_json(**result) | ||
|
||
|
||
def main(): | ||
run_module() | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.