Skip to content

Commit 025c79f

Browse files
genofireopoplawski
authored andcommitted
feat: add virtual-ip module
1 parent 1738f05 commit 025c79f

File tree

1 file changed

+309
-0
lines changed

1 file changed

+309
-0
lines changed
Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
4+
# Copyright: (c) 2018, Frederic Bor <frederic.bor@wanadoo.fr>
5+
# Copyright: (c) 2021, Jan Wenzel <jan.wenzel@gonicus.de>
6+
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
7+
8+
from __future__ import absolute_import, division, print_function
9+
__metaclass__ = type
10+
11+
12+
ANSIBLE_METADATA = {'metadata_version': '1.1',
13+
'status': ['preview'],
14+
'supported_by': 'community'}
15+
16+
DOCUMENTATION = """
17+
---
18+
module: pfsense_virtual_ip
19+
version_added: "0.4.2"
20+
author: Jan Wenzel (@coffeelover)
21+
short_description: Manage pfSense virtual ip settings
22+
description:
23+
- Manage pfSense virtual ip settings
24+
notes:
25+
options:
26+
mode:
27+
description: Type
28+
required: true
29+
type: str
30+
choices: ['proxyarp', 'carp', 'ipalias', 'other']
31+
noexpand:
32+
description: Disable expansion of this entry into IPs on NAT lists (e.g. 192.168.1.0/24 expands to 256 entries.)
33+
required: false
34+
type: bool
35+
descr:
36+
description: Description
37+
required: false
38+
type: str
39+
interface:
40+
description: Interface
41+
required: true
42+
type: str
43+
vhid:
44+
description: VHID Group
45+
required: false
46+
type: int
47+
advbase:
48+
description: Advertising Frequency Base
49+
required: false
50+
type: int
51+
advskew:
52+
description: Advertising Frequency Skew
53+
required: false
54+
type: int
55+
password:
56+
description: Virtual IP Password
57+
required: false
58+
type: str
59+
uniqid:
60+
description: Unique ID of Virtual IP in configuration
61+
required: false
62+
type: str
63+
type:
64+
description: Address Type
65+
required: false
66+
type: str
67+
choices: ['single']
68+
default: single
69+
subnet_bits
70+
description: Network's subnet mask
71+
required: false
72+
type: int
73+
default: 32
74+
subnet
75+
description: Network subnet
76+
required: false
77+
type: str
78+
state:
79+
description: State in which to leave the Virtual IP
80+
choices: [ "present", "absent" ]
81+
default: present
82+
type: str
83+
"""
84+
85+
EXAMPLES = """
86+
"""
87+
88+
RETURN = """
89+
"""
90+
91+
import re
92+
from copy import deepcopy
93+
from ansible.module_utils.basic import AnsibleModule
94+
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
95+
96+
97+
VIRTUALIP_ARGUMENT_SPEC = dict(
98+
mode=dict(required=True, type='str'),
99+
interface=dict(required=True, type='str'),
100+
vhid=dict(required=False, type='int'),
101+
advskew=dict(required=False, type='int'),
102+
advbase=dict(required=False, type='int'),
103+
password=dict(required=False, type='str'),
104+
uniqid=dict(required=False, type='str'),
105+
descr=dict(required=False, type='str'),
106+
type=dict(required=False, type='str', default='single'),
107+
subnet_bits=dict(required=False, type='int', default=32),
108+
subnet=dict(required=False, type='str'),
109+
state=dict(default='present', choices=['present', 'absent'], type='str'),
110+
)
111+
112+
VIRTUALIP_REQUIRED_IF = [
113+
["mode", "carp", ["uniqid", "password", "vhid", "advbase"]],
114+
["mode", "ipalias", ["uniqid"]],
115+
]
116+
117+
# map field names between ansible and pfsense
118+
params_map = {}
119+
120+
# fields with inverted logic
121+
inverted_list = []
122+
123+
# fields that are not written to pfsense
124+
skip_list = ['state']
125+
126+
class PFSenseVirtualIPModule(PFSenseModuleBase):
127+
""" module managing pfsense virtual ip settings """
128+
129+
@staticmethod
130+
def get_argument_spec():
131+
""" return argument spec """
132+
return VIRTUALIP_ARGUMENT_SPEC
133+
134+
##############################
135+
# init
136+
#
137+
def __init__(self, module, pfsense=None):
138+
super(PFSenseVirtualIPModule, self).__init__(module, pfsense)
139+
self.name = "virtual_ip"
140+
self.root_elt = self.pfsense.get_element('virtualip')
141+
self.obj = dict()
142+
143+
if self.root_elt is None:
144+
self.root_elt = self.pfsense.new_element('virtualip')
145+
self.pfsense.root.append(self.root_elt)
146+
147+
##############################
148+
# params processing
149+
#
150+
def _params_to_obj(self):
151+
""" return a dict from module params """
152+
params = self.params
153+
154+
obj = dict()
155+
self.obj = obj
156+
157+
def _set_param(target, param):
158+
if params.get(param) is not None:
159+
if isinstance(params[param], str):
160+
target[param] = params[param]
161+
else:
162+
target[param] = str(params[param])
163+
164+
def _set_param_bool(target, param):
165+
if params.get(param) is not None:
166+
value = params.get(param)
167+
if value is True and param not in target:
168+
target[param] = ''
169+
elif value is False and param in target:
170+
del target[param]
171+
172+
for param in VIRTUALIP_ARGUMENT_SPEC:
173+
if param not in skip_list:
174+
if VIRTUALIP_ARGUMENT_SPEC[param]['type'] == 'bool':
175+
_set_param_bool(obj, param)
176+
else:
177+
_set_param(obj, param)
178+
179+
return obj
180+
181+
182+
def _validate_params(self):
183+
""" do some extra checks on input parameters """
184+
params = self.params
185+
return
186+
187+
##############################
188+
# XML processing
189+
#
190+
def _create_target(self):
191+
""" create the XML target_elt """
192+
return self.pfsense.new_element('vip')
193+
194+
def _find_target(self):
195+
""" find the XML target elt """
196+
for vip_elt in self.root_elt:
197+
if self.params['mode'] in ['ipalias', 'carp']:
198+
if vip_elt.find('uniqid') is not None and vip_elt.find('uniqid').text == self.params['uniqid']:
199+
return vip_elt
200+
else:
201+
if vip_elt.find('descr') is not None and vip_elt.find('descr').text == self.params['descr']:
202+
return vip_elt
203+
return None
204+
205+
def _remove_deleted_params(self):
206+
""" Remove from target_elt a few deleted params """
207+
changed = False
208+
for param in VIRTUALIP_ARGUMENT_SPEC:
209+
if VIRTUALIP_ARGUMENT_SPEC[param]['type'] == 'bool':
210+
if self.pfsense.remove_deleted_param_from_elt(self.target_elt, param, self.obj):
211+
changed = True
212+
213+
return changed
214+
215+
def _update(self):
216+
""" make the target pfsense reload """
217+
cmd = '''
218+
require_once("globals.inc");
219+
require_once("functions.inc");
220+
require_once("filter.inc");
221+
require_once("shaper.inc");
222+
require_once("interfaces.inc");
223+
require_once("util.inc");
224+
$check_carp = false;
225+
$retval = 0;
226+
'''
227+
228+
if self.params.get('mode') in ['carp', 'ipalias']:
229+
cmd += '$uniqid = "' + self.params.get('uniqid') +'";\n'
230+
cmd += '$subnet = "' + self.params.get('subnet') +'";\n'
231+
cmd += '$interface = "' + self.params.get('interface') +'";\n'
232+
cmd += '$vipif = get_real_interface($interface);\n'
233+
234+
if self.params.get('state') == 'present':
235+
if self.params.get('mode') in ['carp', 'ipalias']:
236+
cmd += '$check_carp = true;\n'
237+
cmd += 'foreach ($config["virtualip"]["vip"] as $vip) {\n'
238+
cmd += 'if ($vip["uniqid"] == $uniqid) {\n'
239+
cmd += 'interface_' + self.params.get('mode') + '_configure($vip);\n'
240+
cmd += '}\n}\n'
241+
else:
242+
if self.params.get('mode') == 'carp':
243+
cmd += 'if (does_interface_exist($vipif)) {\n'
244+
cmd += 'if (is_ipaddrv6($subnet)) {\n'
245+
cmd += 'mwexec("/sbin/ifconfig " . escapeshellarg($vipif) . " inet6 " . escapeshellarg($subnet) . " delete");\n'
246+
cmd += '} else {\n'
247+
cmd += 'pfSense_interface_deladdress($vipif, $subnet);\n'
248+
cmd += '}\n}\n'
249+
elif self.params.get('mode') == 'ipalias':
250+
cmd += 'if (does_interface_exist($vipif)) {\n'
251+
cmd += 'if (is_ipaddrv6($subnet)) {\n'
252+
cmd += 'mwexec("/sbin/ifconfig " . escapeshellarg($vipif) . " inet6 " . escapeshellarg($subnet) . " -alias");\n'
253+
cmd += '} else {\n'
254+
cmd += 'pfSense_interface_deladdress($vipif, $subnet);\n'
255+
cmd += '}\n}\n'
256+
257+
cmd += '''
258+
if ($check_carp === true && !get_carp_status()) {
259+
set_single_sysctl("net.inet.carp.allow", "1");
260+
}
261+
$retval |= filter_configure();
262+
$retval |= mwexec("/etc/rc.filter_synchronize");
263+
clear_subsystem_dirty('vip');'''
264+
265+
return self.pfsense.phpshell(cmd)
266+
267+
##############################
268+
# Logging
269+
#
270+
@staticmethod
271+
def _get_obj_name():
272+
""" return obj's name """
273+
return "vip"
274+
275+
def _log_fields(self, before=None):
276+
""" generate pseudo-CLI command fields parameters to create an obj """
277+
values = ''
278+
279+
if before is None:
280+
for param in VIRTUALIP_ARGUMENT_SPEC:
281+
if param not in skip_list:
282+
if VIRTUALIP_ARGUMENT_SPEC[param]['type'] == 'bool':
283+
values += self.format_cli_field(self.obj, param, fvalue=self.fvalue_bool)
284+
else:
285+
values += self.format_cli_field(self.obj, param)
286+
else:
287+
for param in VIRTUALIP_ARGUMENT_SPEC:
288+
if param not in skip_list:
289+
if VIRTUALIP_ARGUMENT_SPEC[param]['type'] == 'bool':
290+
values += self.format_updated_cli_field(self.obj, before, param, fvalue=self.fvalue_bool, add_comma=(values), log_none=False)
291+
else:
292+
values += self.format_updated_cli_field(self.obj, before, param, add_comma=(values), log_none=False)
293+
294+
return values
295+
296+
297+
def main():
298+
module = AnsibleModule(
299+
argument_spec=VIRTUALIP_ARGUMENT_SPEC,
300+
required_if=VIRTUALIP_REQUIRED_IF,
301+
supports_check_mode=True)
302+
303+
pfmodule = PFSenseVirtualIPModule(module)
304+
pfmodule.run(module.params)
305+
pfmodule.commit_changes()
306+
307+
308+
if __name__ == '__main__':
309+
main()

0 commit comments

Comments
 (0)