Skip to content

Commit 13f46d7

Browse files
committed
[pfsense_rule] Change after to insert after the last match instead of the first
1 parent 20dba4a commit 13f46d7

File tree

5 files changed

+292
-6
lines changed

5 files changed

+292
-6
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
minor_changes:
2+
- pfsense_rule - Change `after` to insert after the last match instead of the first.

plugins/module_utils/pfsense.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,18 +229,22 @@ def get_interface_rules_count(self, interface, floating):
229229

230230
return count
231231

232-
def get_rule_position(self, descr, interface, floating):
232+
def get_rule_position(self, descr, interface, floating, first=True):
233233
""" get rule position in interface/floating """
234234
i = 0
235+
found = None
235236
for rule_elt in self.rules:
236237
if not self.rule_match_interface(rule_elt, interface, floating):
237238
continue
238239
descr_elt = rule_elt.find('descr')
239240
if descr_elt is not None and descr_elt.text == descr:
240-
return i
241+
if first:
242+
return i
243+
else:
244+
found = i
241245
i += 1
242246

243-
return None
247+
return found
244248

245249
def copy_dict_to_element(self, src, top_elt, sub=0):
246250
""" Copy/update top_elt from src """

plugins/module_utils/rule.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ def _get_expected_rule_position(self):
410410
elif self._after == 'top':
411411
return 0
412412
elif self._after is not None:
413-
return self._get_rule_position(self._after) + 1
413+
return self._get_rule_position(self._after, first=False) + 1
414414
elif self._before is not None:
415415
position = self._get_rule_position(self._before) - 1
416416
if position < 0:
@@ -472,12 +472,12 @@ def _get_params_to_remove():
472472
""" returns the list of params to remove if they are not set """
473473
return ['log', 'protocol', 'disabled', 'defaultqueue', 'ackqueue', 'dnpipe', 'pdnpipe', 'gateway', 'icmptype', 'sched', 'quick', 'tcpflags_any']
474474

475-
def _get_rule_position(self, descr=None, fail=True):
475+
def _get_rule_position(self, descr=None, fail=True, first=True):
476476
""" get rule position in interface/floating """
477477
if descr is None:
478478
descr = self.obj['descr']
479479

480-
res = self.pfsense.get_rule_position(descr, self.obj['interface'], self._floating)
480+
res = self.pfsense.get_rule_position(descr, self.obj['interface'], self._floating, first=first)
481481
if fail and res is None:
482482
self.module.fail_json(msg='Failed to find rule=%s interface=%s' % (descr, self._interface_name()))
483483
return res

plugins/modules/pfsense_pkg.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
4+
# Copyright: (c) 2024, Orion Poplawski <orion@nwra.com>
5+
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6+
7+
from __future__ import (absolute_import, division, print_function)
8+
__metaclass__ = type
9+
10+
DOCUMENTATION = r'''
11+
---
12+
module: pfsense_pkg
13+
14+
short_description: Manage pfSense configs
15+
16+
version_added: "0.6.0"
17+
18+
description:
19+
- Manage pfSense configs
20+
21+
options:
22+
name:
23+
description: The name of the config
24+
required: true
25+
type: str
26+
state:
27+
description: State in which to leave the config
28+
choices: ['present', 'absent']
29+
default: present
30+
type: str
31+
cmd:
32+
description: Command of the config. Enter the command to run.
33+
type: str
34+
cmdtype:
35+
description: Shellcmd Type of the config. Choose the shellcmd type. Click Info for details.
36+
choices: ['shellcmd', 'earlyshellcmd', 'afterfilterchangeshellcmd', 'disabled']
37+
type: str
38+
39+
author: Orion Poplawski (@)
40+
'''
41+
42+
EXAMPLES = r'''
43+
- name: Add myitem config
44+
pfsensible.core.pfsense_pkg:
45+
name: myitem
46+
cmd: echo hi
47+
cmdtype: shellcmd
48+
state: present
49+
50+
- name: Remove myitem config
51+
pfsensible.core.pfsense_pkg:
52+
name: myitem
53+
state: absent
54+
'''
55+
RETURN = r'''
56+
commands:
57+
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI)
58+
returned: always
59+
type: list
60+
sample: ["create config 'myitem'", "update config 'myitem' set ...", "delete config 'myitem'"]
61+
'''
62+
63+
from ansible.module_utils.basic import AnsibleModule
64+
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
65+
66+
# Change to name of module, extend for needed parameters
67+
CONFIG_ARGUMENT_SPEC = {
68+
# Only name should be required here - othewise you cannot remove an item with just 'name'
69+
# Required arguments for creation should be note in CONFIG_REQUIRED_IF = ['state', 'present', ...] below
70+
'name': {'required': True, 'type': 'str'},
71+
'state': {
72+
'type': 'str',
73+
'default': 'present',
74+
'choices': ['present', 'absent']
75+
},
76+
'cmd': {
77+
'type': 'str',
78+
},
79+
'cmdtype': {
80+
'choices': ['shellcmd', 'earlyshellcmd', 'afterfilterchangeshellcmd', 'disabled'],
81+
'type': 'str',
82+
},
83+
}
84+
85+
# Compact style
86+
CONFIG_ARGUMENT_SPEC = dict(
87+
# Only name should be required here - othewise you cannot remove an item with just 'name'
88+
# Required arguments for creation should be note in CONFIG_REQUIRED_IF = ['state', 'present', ...] below
89+
name=dict(required=True, typ='str'),
90+
state=dict(type='str', default='present', choices=['present', 'absent']),
91+
cmd=dict(type='str'),
92+
cmdtype=dict(type='str', choices=['shellcmd', 'earlyshellcmd', 'afterfilterchangeshellcmd', 'disabled'],),
93+
)
94+
95+
# TODO - check for validity - what are default values when creating a new config
96+
CONFIG_CREATE_DEFAULT = dict(
97+
)
98+
99+
# TODO - check for validity - what parameters are actually required when creating a new config?
100+
CONFIG_REQUIRED_IF = [
101+
['state', 'present', ['{'key': 'cmd', 'value': {'description': 'Command of the config. Enter the command to run.', 'type': 'str', 'required': True, 'example': 'echo hi'}}', '{'key': 'cmdtype', 'value': {'description': 'Shellcmd Type of the config. Choose the shellcmd type. Click Info for details.', 'choices': ['shellcmd', 'earlyshellcmd', 'afterfilterchangeshellcmd', 'disabled'], 'type': 'str', 'multiple': False, 'required': True, 'example': 'shellcmd'}}']],
102+
]
103+
104+
CONFIG_PHP_COMMAND_SET = r'''
105+
require_once("filter.inc");
106+
if (filter_configure() == 0) { clear_subsystem_dirty(''); }
107+
'''
108+
109+
110+
class PFSenseConfigModule(PFSenseModuleBase):
111+
""" module managing pfsense configs """
112+
113+
##############################
114+
# unit tests
115+
#
116+
# Must be class method for unit test usage
117+
@staticmethod
118+
def get_argument_spec():
119+
""" return argument spec """
120+
return CONFIG_ARGUMENT_SPEC
121+
122+
def __init__(self, module, pfsense=None):
123+
super(PFSenseConfigModule, self).__init__(module, pfsense, root='shellcmdsettings', node='config', key='description', update_php=CONFIG_PHP_COMMAND_SET,
124+
+ arg_route=Config_ARG_ROUTE, map_param=Config_MAP_PARAM, create_default=Config_CREATE_DEFAULT)
125+
126+
127+
def main():
128+
module = AnsibleModule(
129+
argument_spec=CONFIG_ARGUMENT_SPEC,
130+
required_if=CONFIG_REQUIRED_IF,
131+
supports_check_mode=True)
132+
133+
pfmodule = PFSenseConfigModule(module)
134+
# Pass params for testing framework
135+
pfmodule.run(module.params)
136+
pfmodule.commit_changes()
137+
138+
139+
if __name__ == '__main__':
140+
main()

plugins/modules/pkg_edit.php.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
4+
# Copyright: (c) 2024, Orion Poplawski <orion@nwra.com>
5+
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6+
7+
from __future__ import (absolute_import, division, print_function)
8+
__metaclass__ = type
9+
10+
DOCUMENTATION = r'''
11+
---
12+
module: /pkg_edit.php
13+
14+
short_description: Manage pfSense configs
15+
16+
version_added: "0.6.0"
17+
18+
description:
19+
- Manage pfSense configs
20+
21+
options:
22+
name:
23+
description: The name of the config
24+
required: true
25+
type: str
26+
state:
27+
description: State in which to leave the config
28+
choices: ['present', 'absent']
29+
default: present
30+
type: str
31+
cmd:
32+
description: Command of the config. Enter the command to run.
33+
type: str
34+
cmdtype:
35+
description: Shellcmd Type of the config. Choose the shellcmd type. Click Info for details.
36+
choices: ['shellcmd', 'earlyshellcmd', 'afterfilterchangeshellcmd', 'disabled']
37+
type: str
38+
39+
author: Orion Poplawski (@)
40+
'''
41+
42+
EXAMPLES = r'''
43+
- name: Add myitem config
44+
pfsensible.core./pkg_edit.php:
45+
name: myitem
46+
cmd: echo hi
47+
cmdtype: shellcmd
48+
state: present
49+
50+
- name: Remove myitem config
51+
pfsensible.core./pkg_edit.php:
52+
name: myitem
53+
state: absent
54+
'''
55+
RETURN = r'''
56+
commands:
57+
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI)
58+
returned: always
59+
type: list
60+
sample: ["create config 'myitem'", "update config 'myitem' set ...", "delete config 'myitem'"]
61+
'''
62+
63+
from ansible.module_utils.basic import AnsibleModule
64+
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
65+
66+
# Change to name of module, extend for needed parameters
67+
CONFIG_ARGUMENT_SPEC = {
68+
# Only name should be required here - othewise you cannot remove an item with just 'name'
69+
# Required arguments for creation should be note in CONFIG_REQUIRED_IF = ['state', 'present', ...] below
70+
'name': {'required': True, 'type': 'str'},
71+
'state': {
72+
'type': 'str',
73+
'default': 'present',
74+
'choices': ['present', 'absent']
75+
},
76+
'cmd': {
77+
'type': 'str',
78+
},
79+
'cmdtype': {
80+
'choices': ['shellcmd', 'earlyshellcmd', 'afterfilterchangeshellcmd', 'disabled'],
81+
'type': 'str',
82+
},
83+
}
84+
85+
# Compact style
86+
CONFIG_ARGUMENT_SPEC = dict(
87+
# Only name should be required here - othewise you cannot remove an item with just 'name'
88+
# Required arguments for creation should be note in CONFIG_REQUIRED_IF = ['state', 'present', ...] below
89+
name=dict(required=True, typ='str'),
90+
state=dict(type='str', default='present', choices=['present', 'absent']),
91+
cmd=dict(type='str'),
92+
cmdtype=dict(type='str', choices=['shellcmd', 'earlyshellcmd', 'afterfilterchangeshellcmd', 'disabled'],),
93+
)
94+
95+
# TODO - check for validity - what are default values when creating a new config
96+
CONFIG_CREATE_DEFAULT = dict(
97+
)
98+
99+
# TODO - check for validity - what parameters are actually required when creating a new config?
100+
CONFIG_REQUIRED_IF = [
101+
['state', 'present', ['{'key': 'cmd', 'value': {'description': 'Command of the config. Enter the command to run.', 'type': 'str', 'required': True, 'example': 'echo hi'}}', '{'key': 'cmdtype', 'value': {'description': 'Shellcmd Type of the config. Choose the shellcmd type. Click Info for details.', 'choices': ['shellcmd', 'earlyshellcmd', 'afterfilterchangeshellcmd', 'disabled'], 'type': 'str', 'multiple': False, 'required': True, 'example': 'shellcmd'}}']],
102+
]
103+
104+
CONFIG_PHP_COMMAND_SET = r'''
105+
require_once("filter.inc");
106+
if (filter_configure() == 0) { clear_subsystem_dirty(''); }
107+
'''
108+
109+
110+
class PFSenseConfigModule(PFSenseModuleBase):
111+
""" module managing pfsense configs """
112+
113+
##############################
114+
# unit tests
115+
#
116+
# Must be class method for unit test usage
117+
@staticmethod
118+
def get_argument_spec():
119+
""" return argument spec """
120+
return CONFIG_ARGUMENT_SPEC
121+
122+
def __init__(self, module, pfsense=None):
123+
super(PFSenseConfigModule, self).__init__(module, pfsense, root='shellcmdsettings', node='config', key='description', update_php=CONFIG_PHP_COMMAND_SET,
124+
+ arg_route=Config_ARG_ROUTE, map_param=Config_MAP_PARAM, create_default=Config_CREATE_DEFAULT)
125+
126+
127+
def main():
128+
module = AnsibleModule(
129+
argument_spec=CONFIG_ARGUMENT_SPEC,
130+
required_if=CONFIG_REQUIRED_IF,
131+
supports_check_mode=True)
132+
133+
pfmodule = PFSenseConfigModule(module)
134+
# Pass params for testing framework
135+
pfmodule.run(module.params)
136+
pfmodule.commit_changes()
137+
138+
139+
if __name__ == '__main__':
140+
main()

0 commit comments

Comments
 (0)